From cf1b3e156fc5583614992f712ef6ad14b67cb4d5 Mon Sep 17 00:00:00 2001 From: Patryk Zdunowski Date: Sun, 2 Aug 2026 02:18:11 +0200 Subject: [PATCH 1/5] feat(opencli2rust): add Rust CLI generator (clap v4) with regen-safe custom-code seams --- .github/workflows/tests-opencli-pipeline.yml | 45 +- packages/xyd-opencli2rust/README.md | 123 ++++ .../input.json | 73 +++ .../output.rs | 63 ++ .../recorded.json | 19 + .../input.json | 61 ++ .../output.rs | 55 ++ .../recorded.json | 15 + .../input.json | 97 +++ .../output.rs | 73 +++ .../recorded.json | 14 + .../input.json | 97 +++ .../output.rs | 73 +++ .../recorded.json | 14 + .../input.json | 254 ++++++++ .../output.rs | 134 ++++ .../recorded.json | 14 + .../input.json | 90 +++ .../output.rs | 72 +++ .../recorded.json | 19 + .../input.json | 90 +++ .../output.rs | 72 +++ .../recorded.json | 19 + .../input.json | 90 +++ .../output.rs | 72 +++ .../recorded.json | 19 + .../input.json | 62 ++ .../output.rs | 56 ++ .../recorded.json | 15 + .../input.json | 100 +++ .../output.rs | 76 +++ .../recorded.json | 14 + .../input.json | 100 +++ .../output.rs | 76 +++ .../recorded.json | 14 + .../input.json | 90 +++ .../output.rs | 72 +++ .../recorded.json | 19 + .../input.json | 74 +++ .../output.rs | 64 ++ .../recorded.json | 19 + .../input.json | 62 ++ .../output.rs | 56 ++ .../recorded.json | 15 + .../input.json | 100 +++ .../output.rs | 76 +++ .../recorded.json | 14 + .../input.json | 100 +++ .../output.rs | 76 +++ .../recorded.json | 14 + .../input.json | 94 +++ .../output.rs | 75 +++ .../recorded.json | 21 + .../input.json | 78 +++ .../output.rs | 67 ++ .../recorded.json | 17 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 74 +++ .../output.rs | 64 ++ .../recorded.json | 19 + .../input.json | 94 +++ .../output.rs | 75 +++ .../recorded.json | 21 + .../input.json | 78 +++ .../output.rs | 67 ++ .../recorded.json | 17 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 115 ++++ .../output.rs | 83 +++ .../recorded.json | 22 + .../input.json | 62 ++ .../output.rs | 56 ++ .../recorded.json | 15 + .../input.json | 81 +++ .../output.rs | 68 ++ .../recorded.json | 14 + .../input.json | 81 +++ .../output.rs | 68 ++ .../recorded.json | 14 + .../input.json | 78 +++ .../output.rs | 67 ++ .../recorded.json | 17 + .../input.json | 99 +++ .../output.rs | 79 +++ .../recorded.json | 16 + .../input.json | 99 +++ .../output.rs | 79 +++ .../recorded.json | 16 + .../input.json | 106 ++++ .../output.rs | 84 +++ .../recorded.json | 19 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 106 ++++ .../output.rs | 84 +++ .../recorded.json | 19 + .../input.json | 112 ++++ .../output.rs | 84 +++ .../recorded.json | 24 + .../input.json | 78 +++ .../output.rs | 67 ++ .../recorded.json | 17 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 91 +++ .../output.rs | 80 +++ .../recorded.json | 14 + .../input.json | 114 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 183 ++++++ .../output.rs | 126 ++++ .../recorded.json | 18 + .../input.json | 91 +++ .../output.rs | 80 +++ .../recorded.json | 14 + .../input.json | 94 +++ .../output.rs | 75 +++ .../recorded.json | 21 + .../input.json | 78 +++ .../output.rs | 67 ++ .../recorded.json | 17 + .../input.json | 99 +++ .../output.rs | 79 +++ .../recorded.json | 16 + .../input.json | 99 +++ .../output.rs | 79 +++ .../recorded.json | 16 + .../input.json | 94 +++ .../output.rs | 75 +++ .../recorded.json | 21 + .../input.json | 106 ++++ .../output.rs | 84 +++ .../recorded.json | 19 + .../input.json | 126 ++++ .../output.rs | 95 +++ .../recorded.json | 21 + .../input.json | 78 +++ .../output.rs | 67 ++ .../recorded.json | 17 + .../input.json | 99 +++ .../output.rs | 79 +++ .../recorded.json | 16 + .../input.json | 99 +++ .../output.rs | 79 +++ .../recorded.json | 16 + .../input.json | 126 ++++ .../output.rs | 95 +++ .../recorded.json | 21 + .../input.json | 111 ++++ .../output.rs | 85 +++ .../recorded.json | 22 + .../input.json | 62 ++ .../output.rs | 56 ++ .../recorded.json | 15 + .../input.json | 100 +++ .../output.rs | 76 +++ .../recorded.json | 14 + .../input.json | 100 +++ .../output.rs | 76 +++ .../recorded.json | 14 + .../input.json | 111 ++++ .../output.rs | 85 +++ .../recorded.json | 22 + .../input.json | 62 ++ .../output.rs | 56 ++ .../recorded.json | 15 + .../input.json | 99 +++ .../output.rs | 78 +++ .../recorded.json | 14 + .../input.json | 99 +++ .../output.rs | 78 +++ .../recorded.json | 14 + .../input.json | 94 +++ .../output.rs | 75 +++ .../recorded.json | 21 + .../input.json | 78 +++ .../output.rs | 67 ++ .../recorded.json | 17 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../input.json | 135 ++++ .../output.rs | 101 +++ .../recorded.json | 16 + .../audio__speech__create/input.json | 186 ++++++ .../audio__speech__create/output.rs | 119 ++++ .../audio__speech__create/recorded.json | 25 + .../audio__transcriptions__create/input.json | 277 ++++++++ .../audio__transcriptions__create/output.rs | 166 +++++ .../recorded.json | 22 + .../audio__translations__create/input.json | 147 +++++ .../audio__translations__create/output.rs | 100 +++ .../audio__translations__create/recorded.json | 22 + .../batches__cancel/input.json | 149 +++++ .../batches__cancel/output.rs | 96 +++ .../batches__cancel/recorded.json | 24 + .../batches__create/input.json | 149 +++++ .../batches__create/output.rs | 96 +++ .../batches__create/recorded.json | 24 + .../batches__list/input.json | 76 +++ .../-2.complex.openai/batches__list/output.rs | 63 ++ .../batches__list/recorded.json | 13 + .../batches__retrieve/input.json | 76 +++ .../batches__retrieve/output.rs | 63 ++ .../batches__retrieve/recorded.json | 13 + .../beta__assistants__create/input.json | 223 +++++++ .../beta__assistants__create/output.rs | 152 +++++ .../beta__assistants__create/recorded.json | 18 + .../beta__assistants__delete/input.json | 57 ++ .../beta__assistants__delete/output.rs | 51 ++ .../beta__assistants__delete/recorded.json | 14 + .../beta__assistants__list/input.json | 110 ++++ .../beta__assistants__list/output.rs | 79 +++ .../beta__assistants__list/recorded.json | 13 + .../beta__assistants__retrieve/input.json | 110 ++++ .../beta__assistants__retrieve/output.rs | 79 +++ .../beta__assistants__retrieve/recorded.json | 13 + .../beta__assistants__update/input.json | 223 +++++++ .../beta__assistants__update/output.rs | 152 +++++ .../beta__assistants__update/recorded.json | 18 + .../input.json | 140 ++++ .../beta__chatkit__sessions__cancel/output.rs | 105 +++ .../recorded.json | 22 + .../input.json | 140 ++++ .../beta__chatkit__sessions__create/output.rs | 105 +++ .../recorded.json | 22 + .../beta__chatkit__threads__delete/input.json | 62 ++ .../beta__chatkit__threads__delete/output.rs | 56 ++ .../recorded.json | 15 + .../input.json | 65 ++ .../output.rs | 57 ++ .../recorded.json | 15 + .../beta__chatkit__threads__list/input.json | 65 ++ .../beta__chatkit__threads__list/output.rs | 57 ++ .../recorded.json | 15 + .../input.json | 65 ++ .../output.rs | 57 ++ .../recorded.json | 15 + .../beta__threads__create-and-run/input.json | 100 +++ .../beta__threads__create-and-run/output.rs | 78 +++ .../recorded.json | 14 + .../beta__threads__create/input.json | 100 +++ .../beta__threads__create/output.rs | 78 +++ .../beta__threads__create/recorded.json | 14 + .../beta__threads__delete/input.json | 57 ++ .../beta__threads__delete/output.rs | 51 ++ .../beta__threads__delete/recorded.json | 14 + .../input.json | 140 ++++ .../beta__threads__messages__create/output.rs | 98 +++ .../recorded.json | 23 + .../input.json | 73 +++ .../beta__threads__messages__delete/output.rs | 62 ++ .../recorded.json | 16 + .../beta__threads__messages__list/input.json | 143 +++++ .../beta__threads__messages__list/output.rs | 98 +++ .../recorded.json | 15 + .../input.json | 143 +++++ .../output.rs | 98 +++ .../recorded.json | 15 + .../input.json | 140 ++++ .../beta__threads__messages__update/output.rs | 98 +++ .../recorded.json | 23 + .../beta__threads__retrieve/input.json | 60 ++ .../beta__threads__retrieve/output.rs | 52 ++ .../beta__threads__retrieve/recorded.json | 14 + .../beta__threads__runs__cancel/input.json | 304 +++++++++ .../beta__threads__runs__cancel/output.rs | 209 ++++++ .../beta__threads__runs__cancel/recorded.json | 19 + .../beta__threads__runs__create/input.json | 304 +++++++++ .../beta__threads__runs__create/output.rs | 209 ++++++ .../beta__threads__runs__create/recorded.json | 19 + .../beta__threads__runs__list/input.json | 128 ++++ .../beta__threads__runs__list/output.rs | 90 +++ .../beta__threads__runs__list/recorded.json | 15 + .../beta__threads__runs__retrieve/input.json | 128 ++++ .../beta__threads__runs__retrieve/output.rs | 90 +++ .../recorded.json | 15 + .../input.json | 165 +++++ .../output.rs | 111 ++++ .../recorded.json | 17 + .../input.json | 165 +++++ .../output.rs | 111 ++++ .../recorded.json | 17 + .../input.json | 304 +++++++++ .../output.rs | 209 ++++++ .../recorded.json | 19 + .../beta__threads__runs__update/input.json | 304 +++++++++ .../beta__threads__runs__update/output.rs | 209 ++++++ .../beta__threads__runs__update/recorded.json | 19 + .../beta__threads__update/input.json | 100 +++ .../beta__threads__update/output.rs | 78 +++ .../beta__threads__update/recorded.json | 14 + .../chat__completions__create/input.json | 600 ++++++++++++++++++ .../chat__completions__create/output.rs | 393 ++++++++++++ .../chat__completions__create/recorded.json | 22 + .../chat__completions__delete/input.json | 62 ++ .../chat__completions__delete/output.rs | 56 ++ .../chat__completions__delete/recorded.json | 15 + .../chat__completions__list/input.json | 130 ++++ .../chat__completions__list/output.rs | 92 +++ .../chat__completions__list/recorded.json | 14 + .../input.json | 118 ++++ .../output.rs | 87 +++ .../recorded.json | 16 + .../chat__completions__retrieve/input.json | 130 ++++ .../chat__completions__retrieve/output.rs | 92 +++ .../chat__completions__retrieve/recorded.json | 14 + .../chat__completions__update/input.json | 600 ++++++++++++++++++ .../chat__completions__update/output.rs | 393 ++++++++++++ .../chat__completions__update/recorded.json | 22 + .../completions__create/input.json | 332 ++++++++++ .../completions__create/output.rs | 222 +++++++ .../completions__create/recorded.json | 21 + .../containers__create/input.json | 161 +++++ .../containers__create/output.rs | 107 ++++ .../containers__create/recorded.json | 18 + .../containers__delete/input.json | 57 ++ .../containers__delete/output.rs | 51 ++ .../containers__delete/recorded.json | 14 + .../input.json | 76 +++ .../output.rs | 65 ++ .../recorded.json | 17 + .../containers__files__create/input.json | 102 +++ .../containers__files__create/output.rs | 76 +++ .../containers__files__create/recorded.json | 16 + .../containers__files__delete/input.json | 71 +++ .../containers__files__delete/output.rs | 60 ++ .../containers__files__delete/recorded.json | 16 + .../containers__files__list/input.json | 112 ++++ .../containers__files__list/output.rs | 81 +++ .../containers__files__list/recorded.json | 15 + .../containers__files__retrieve/input.json | 112 ++++ .../containers__files__retrieve/output.rs | 81 +++ .../containers__files__retrieve/recorded.json | 15 + .../containers__list/input.json | 110 ++++ .../containers__list/output.rs | 79 +++ .../containers__list/recorded.json | 13 + .../containers__retrieve/input.json | 110 ++++ .../containers__retrieve/output.rs | 79 +++ .../containers__retrieve/recorded.json | 13 + .../conversations__create/input.json | 81 +++ .../conversations__create/output.rs | 68 ++ .../conversations__create/recorded.json | 14 + .../conversations__delete/input.json | 57 ++ .../conversations__delete/output.rs | 51 ++ .../conversations__delete/recorded.json | 14 + .../conversations__items__create/input.json | 120 ++++ .../conversations__items__create/output.rs | 83 +++ .../recorded.json | 20 + .../conversations__items__delete/input.json | 73 +++ .../conversations__items__delete/output.rs | 62 ++ .../recorded.json | 16 + .../conversations__items__list/input.json | 141 ++++ .../conversations__items__list/output.rs | 92 +++ .../conversations__items__list/recorded.json | 15 + .../conversations__items__retrieve/input.json | 141 ++++ .../conversations__items__retrieve/output.rs | 92 +++ .../recorded.json | 15 + .../conversations__retrieve/input.json | 60 ++ .../conversations__retrieve/output.rs | 52 ++ .../conversations__retrieve/recorded.json | 14 + .../conversations__update/input.json | 81 +++ .../conversations__update/output.rs | 68 ++ .../conversations__update/recorded.json | 14 + .../embeddings__create/input.json | 139 ++++ .../embeddings__create/output.rs | 97 +++ .../embeddings__create/recorded.json | 21 + .../files__content/input.json | 110 ++++ .../files__content/output.rs | 79 +++ .../files__content/recorded.json | 13 + .../files__create/input.json | 111 ++++ .../-2.complex.openai/files__create/output.rs | 78 +++ .../files__create/recorded.json | 21 + .../files__delete/input.json | 57 ++ .../-2.complex.openai/files__delete/output.rs | 51 ++ .../files__delete/recorded.json | 14 + .../-2.complex.openai/files__list/input.json | 110 ++++ .../-2.complex.openai/files__list/output.rs | 79 +++ .../files__list/recorded.json | 13 + .../files__retrieve/input.json | 110 ++++ .../files__retrieve/output.rs | 79 +++ .../files__retrieve/recorded.json | 13 + .../input.json | 97 +++ .../output.rs | 77 +++ .../recorded.json | 21 + .../input.json | 78 +++ .../output.rs | 67 ++ .../recorded.json | 17 + .../input.json | 133 ++++ .../output.rs | 95 +++ .../recorded.json | 16 + .../fine-tuning__jobs__cancel/input.json | 206 ++++++ .../fine-tuning__jobs__cancel/output.rs | 139 ++++ .../fine-tuning__jobs__cancel/recorded.json | 22 + .../input.json | 99 +++ .../output.rs | 79 +++ .../recorded.json | 16 + .../fine-tuning__jobs__create/input.json | 206 ++++++ .../fine-tuning__jobs__create/output.rs | 139 ++++ .../fine-tuning__jobs__create/recorded.json | 22 + .../fine-tuning__jobs__list-events/input.json | 98 +++ .../fine-tuning__jobs__list-events/output.rs | 76 +++ .../recorded.json | 14 + .../fine-tuning__jobs__list/input.json | 98 +++ .../fine-tuning__jobs__list/output.rs | 76 +++ .../fine-tuning__jobs__list/recorded.json | 14 + .../fine-tuning__jobs__pause/input.json | 206 ++++++ .../fine-tuning__jobs__pause/output.rs | 139 ++++ .../fine-tuning__jobs__pause/recorded.json | 22 + .../fine-tuning__jobs__resume/input.json | 206 ++++++ .../fine-tuning__jobs__resume/output.rs | 139 ++++ .../fine-tuning__jobs__resume/recorded.json | 22 + .../fine-tuning__jobs__retrieve/input.json | 98 +++ .../fine-tuning__jobs__retrieve/output.rs | 76 +++ .../fine-tuning__jobs__retrieve/recorded.json | 14 + .../models__delete/input.json | 57 ++ .../models__delete/output.rs | 51 ++ .../models__delete/recorded.json | 14 + .../-2.complex.openai/models__list/input.json | 42 ++ .../-2.complex.openai/models__list/output.rs | 45 ++ .../models__list/recorded.json | 13 + .../models__retrieve/input.json | 42 ++ .../models__retrieve/output.rs | 45 ++ .../models__retrieve/recorded.json | 13 + .../moderations__create/input.json | 85 +++ .../moderations__create/output.rs | 71 +++ .../moderations__create/recorded.json | 18 + .../realtime__calls__accept/input.json | 90 +++ .../realtime__calls__accept/output.rs | 74 +++ .../realtime__calls__accept/recorded.json | 19 + .../realtime__calls__hangup/input.json | 90 +++ .../realtime__calls__hangup/output.rs | 74 +++ .../realtime__calls__hangup/recorded.json | 19 + .../realtime__calls__refer/input.json | 90 +++ .../realtime__calls__refer/output.rs | 74 +++ .../realtime__calls__refer/recorded.json | 19 + .../realtime__calls__reject/input.json | 90 +++ .../realtime__calls__reject/output.rs | 74 +++ .../realtime__calls__reject/recorded.json | 19 + .../input.json | 88 +++ .../output.rs | 75 +++ .../recorded.json | 15 + .../responses__cancel/input.json | 498 +++++++++++++++ .../responses__cancel/output.rs | 313 +++++++++ .../responses__cancel/recorded.json | 14 + .../responses__compact/input.json | 498 +++++++++++++++ .../responses__compact/output.rs | 313 +++++++++ .../responses__compact/recorded.json | 14 + .../responses__create/input.json | 498 +++++++++++++++ .../responses__create/output.rs | 313 +++++++++ .../responses__create/recorded.json | 14 + .../responses__delete/input.json | 57 ++ .../responses__delete/output.rs | 51 ++ .../responses__delete/recorded.json | 14 + .../responses__input-items__list/input.json | 141 ++++ .../responses__input-items__list/output.rs | 92 +++ .../recorded.json | 15 + .../responses__input-tokens__count/input.json | 226 +++++++ .../responses__input-tokens__count/output.rs | 153 +++++ .../recorded.json | 15 + .../responses__retrieve/input.json | 125 ++++ .../responses__retrieve/output.rs | 96 +++ .../responses__retrieve/recorded.json | 14 + .../skills__content__retrieve/input.json | 62 ++ .../skills__content__retrieve/output.rs | 56 ++ .../skills__content__retrieve/recorded.json | 15 + .../skills__create/input.json | 68 ++ .../skills__create/output.rs | 60 ++ .../skills__create/recorded.json | 18 + .../skills__delete/input.json | 57 ++ .../skills__delete/output.rs | 51 ++ .../skills__delete/recorded.json | 14 + .../-2.complex.openai/skills__list/input.json | 95 +++ .../-2.complex.openai/skills__list/output.rs | 71 +++ .../skills__list/recorded.json | 13 + .../skills__retrieve/input.json | 95 +++ .../skills__retrieve/output.rs | 71 +++ .../skills__retrieve/recorded.json | 13 + .../skills__update/input.json | 68 ++ .../skills__update/output.rs | 60 ++ .../skills__update/recorded.json | 18 + .../input.json | 78 +++ .../output.rs | 67 ++ .../recorded.json | 17 + .../skills__versions__create/input.json | 99 +++ .../skills__versions__create/output.rs | 83 +++ .../skills__versions__create/recorded.json | 20 + .../skills__versions__delete/input.json | 73 +++ .../skills__versions__delete/output.rs | 62 ++ .../skills__versions__delete/recorded.json | 16 + .../skills__versions__list/input.json | 113 ++++ .../skills__versions__list/output.rs | 82 +++ .../skills__versions__list/recorded.json | 15 + .../skills__versions__retrieve/input.json | 113 ++++ .../skills__versions__retrieve/output.rs | 82 +++ .../skills__versions__retrieve/recorded.json | 15 + .../uploads__cancel/input.json | 145 +++++ .../uploads__cancel/output.rs | 97 +++ .../uploads__cancel/recorded.json | 27 + .../uploads__complete/input.json | 145 +++++ .../uploads__complete/output.rs | 97 +++ .../uploads__complete/recorded.json | 27 + .../uploads__create/input.json | 145 +++++ .../uploads__create/output.rs | 97 +++ .../uploads__create/recorded.json | 27 + .../uploads__parts__create/input.json | 89 +++ .../uploads__parts__create/output.rs | 70 ++ .../uploads__parts__create/recorded.json | 20 + .../vector-stores__create/input.json | 149 +++++ .../vector-stores__create/output.rs | 105 +++ .../vector-stores__create/recorded.json | 14 + .../vector-stores__delete/input.json | 57 ++ .../vector-stores__delete/output.rs | 51 ++ .../vector-stores__delete/recorded.json | 14 + .../input.json | 140 ++++ .../output.rs | 100 +++ .../recorded.json | 16 + .../input.json | 140 ++++ .../output.rs | 100 +++ .../recorded.json | 16 + .../input.json | 76 +++ .../output.rs | 63 ++ .../recorded.json | 16 + .../input.json | 76 +++ .../output.rs | 63 ++ .../recorded.json | 16 + .../vector-stores__files__content/input.json | 149 +++++ .../vector-stores__files__content/output.rs | 98 +++ .../recorded.json | 15 + .../vector-stores__files__create/input.json | 120 ++++ .../vector-stores__files__create/output.rs | 89 +++ .../recorded.json | 20 + .../vector-stores__files__delete/input.json | 73 +++ .../vector-stores__files__delete/output.rs | 62 ++ .../recorded.json | 16 + .../vector-stores__files__list/input.json | 149 +++++ .../vector-stores__files__list/output.rs | 98 +++ .../vector-stores__files__list/recorded.json | 15 + .../vector-stores__files__retrieve/input.json | 149 +++++ .../vector-stores__files__retrieve/output.rs | 98 +++ .../recorded.json | 15 + .../vector-stores__files__update/input.json | 120 ++++ .../vector-stores__files__update/output.rs | 89 +++ .../recorded.json | 20 + .../vector-stores__list/input.json | 110 ++++ .../vector-stores__list/output.rs | 79 +++ .../vector-stores__list/recorded.json | 13 + .../vector-stores__retrieve/input.json | 110 ++++ .../vector-stores__retrieve/output.rs | 79 +++ .../vector-stores__retrieve/recorded.json | 13 + .../vector-stores__search/input.json | 149 +++++ .../vector-stores__search/output.rs | 105 +++ .../vector-stores__search/recorded.json | 14 + .../vector-stores__update/input.json | 149 +++++ .../vector-stores__update/output.rs | 105 +++ .../vector-stores__update/recorded.json | 14 + .../videos__create-character/input.json | 144 +++++ .../videos__create-character/output.rs | 95 +++ .../videos__create-character/recorded.json | 18 + .../videos__create/input.json | 144 +++++ .../videos__create/output.rs | 95 +++ .../videos__create/recorded.json | 18 + .../videos__delete/input.json | 57 ++ .../videos__delete/output.rs | 51 ++ .../videos__delete/recorded.json | 14 + .../videos__download-content/input.json | 95 +++ .../videos__download-content/output.rs | 71 +++ .../videos__download-content/recorded.json | 13 + .../-2.complex.openai/videos__edit/input.json | 144 +++++ .../-2.complex.openai/videos__edit/output.rs | 95 +++ .../videos__edit/recorded.json | 18 + .../videos__extend/input.json | 144 +++++ .../videos__extend/output.rs | 95 +++ .../videos__extend/recorded.json | 18 + .../videos__get-character/input.json | 95 +++ .../videos__get-character/output.rs | 71 +++ .../videos__get-character/recorded.json | 13 + .../-2.complex.openai/videos__list/input.json | 95 +++ .../-2.complex.openai/videos__list/output.rs | 71 +++ .../videos__list/recorded.json | 13 + .../videos__remix/input.json | 144 +++++ .../-2.complex.openai/videos__remix/output.rs | 95 +++ .../videos__remix/recorded.json | 18 + .../videos__retrieve/input.json | 95 +++ .../videos__retrieve/output.rs | 71 +++ .../videos__retrieve/recorded.json | 13 + .../__fixtures__/1.basic/input.json | 74 +++ .../__fixtures__/1.basic/output/.gitignore | 1 + .../__fixtures__/1.basic/output/Cargo.toml | 17 + .../1.basic/output/src/custom/mod.rs | 29 + .../1.basic/output/src/gen/cli.rs | 53 ++ .../1.basic/output/src/gen/cmd/mod.rs | 3 + .../1.basic/output/src/gen/cmd/models.rs | 74 +++ .../1.basic/output/src/gen/mod.rs | 5 + .../1.basic/output/src/gen/runtime/config.rs | 23 + .../1.basic/output/src/gen/runtime/custom.rs | 103 +++ .../1.basic/output/src/gen/runtime/http.rs | 50 ++ .../1.basic/output/src/gen/runtime/mod.rs | 103 +++ .../output/src/gen/runtime/overrides.rs | 51 ++ .../__fixtures__/1.basic/output/src/main.rs | 11 + .../__fixtures__/2.crud/input.json | 281 ++++++++ .../__fixtures__/2.crud/output/.gitignore | 1 + .../__fixtures__/2.crud/output/Cargo.toml | 17 + .../2.crud/output/src/custom/mod.rs | 29 + .../__fixtures__/2.crud/output/src/gen/cli.rs | 52 ++ .../2.crud/output/src/gen/cmd/mod.rs | 3 + .../2.crud/output/src/gen/cmd/widgets.rs | 240 +++++++ .../__fixtures__/2.crud/output/src/gen/mod.rs | 5 + .../2.crud/output/src/gen/runtime/config.rs | 23 + .../2.crud/output/src/gen/runtime/custom.rs | 103 +++ .../2.crud/output/src/gen/runtime/http.rs | 50 ++ .../2.crud/output/src/gen/runtime/mod.rs | 103 +++ .../output/src/gen/runtime/overrides.rs | 51 ++ .../__fixtures__/2.crud/output/src/main.rs | 11 + .../__fixtures__/3.nested/input.json | 196 ++++++ .../__fixtures__/3.nested/output/.gitignore | 1 + .../__fixtures__/3.nested/output/Cargo.toml | 17 + .../3.nested/output/src/custom/mod.rs | 29 + .../3.nested/output/src/gen/cli.rs | 52 ++ .../3.nested/output/src/gen/cmd/mod.rs | 3 + .../3.nested/output/src/gen/cmd/stores.rs | 177 ++++++ .../3.nested/output/src/gen/mod.rs | 5 + .../3.nested/output/src/gen/runtime/config.rs | 18 + .../3.nested/output/src/gen/runtime/custom.rs | 103 +++ .../3.nested/output/src/gen/runtime/http.rs | 50 ++ .../3.nested/output/src/gen/runtime/mod.rs | 103 +++ .../output/src/gen/runtime/overrides.rs | 51 ++ .../__fixtures__/3.nested/output/src/main.rs | 11 + .../__fixtures__/4.body-flatten/input.json | 153 +++++ .../4.body-flatten/output/.gitignore | 1 + .../4.body-flatten/output/Cargo.toml | 17 + .../4.body-flatten/output/src/custom/mod.rs | 29 + .../4.body-flatten/output/src/gen/cli.rs | 52 ++ .../4.body-flatten/output/src/gen/cmd/chat.rs | 113 ++++ .../4.body-flatten/output/src/gen/cmd/mod.rs | 3 + .../4.body-flatten/output/src/gen/mod.rs | 5 + .../output/src/gen/runtime/config.rs | 23 + .../output/src/gen/runtime/custom.rs | 103 +++ .../output/src/gen/runtime/http.rs | 50 ++ .../output/src/gen/runtime/mod.rs | 103 +++ .../output/src/gen/runtime/overrides.rs | 51 ++ .../4.body-flatten/output/src/main.rs | 11 + .../__fixtures__/5.custom-scaffold/custom.rs | 33 + .../__fixtures__/5.custom-scaffold/input.json | 74 +++ .../__tests__/custom-scaffold.test.ts | 80 +++ .../xyd-opencli2rust/__tests__/docs.test.ts | 87 +++ .../xyd-opencli2rust/__tests__/e2e/harness.ts | 289 +++++++++ .../__tests__/e2e/openai.test.ts | 10 + .../__tests__/opencli2rust.test.ts | 21 + packages/xyd-opencli2rust/__tests__/utils.ts | 96 +++ packages/xyd-opencli2rust/biome.json | 25 + packages/xyd-opencli2rust/index.ts | 1 + packages/xyd-opencli2rust/package.json | 26 + packages/xyd-opencli2rust/src/cli.ts | 72 +++ packages/xyd-opencli2rust/src/command.ts | 104 +++ packages/xyd-opencli2rust/src/flags.ts | 55 ++ packages/xyd-opencli2rust/src/handler.ts | 175 +++++ packages/xyd-opencli2rust/src/index.ts | 3 + packages/xyd-opencli2rust/src/main.ts | 20 + packages/xyd-opencli2rust/src/model.ts | 153 +++++ packages/xyd-opencli2rust/src/naming.ts | 56 ++ packages/xyd-opencli2rust/src/project.ts | 59 ++ packages/xyd-opencli2rust/src/rslit.ts | 91 +++ packages/xyd-opencli2rust/src/runtime.ts | 478 ++++++++++++++ packages/xyd-opencli2rust/src/types.ts | 13 + packages/xyd-opencli2rust/src/write.ts | 18 + packages/xyd-opencli2rust/tsconfig.json | 18 + packages/xyd-opencli2rust/tsup.config.ts | 19 + packages/xyd-opencli2rust/vitest.config.ts | 8 + 688 files changed, 51822 insertions(+), 8 deletions(-) create mode 100644 packages/xyd-opencli2rust/README.md create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/output.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/recorded.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/.gitignore create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/Cargo.toml create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/custom/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cli.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cmd/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cmd/models.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/config.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/custom.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/http.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/overrides.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/main.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/.gitignore create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/Cargo.toml create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/custom/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cli.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cmd/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cmd/widgets.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/config.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/custom.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/http.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/overrides.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/main.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/.gitignore create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/Cargo.toml create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/custom/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cli.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cmd/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cmd/stores.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/config.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/custom.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/http.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/overrides.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/main.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/input.json create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/.gitignore create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/Cargo.toml create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/custom/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cli.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cmd/chat.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cmd/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/config.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/custom.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/http.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/mod.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/overrides.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/main.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/5.custom-scaffold/custom.rs create mode 100644 packages/xyd-opencli2rust/__fixtures__/5.custom-scaffold/input.json create mode 100644 packages/xyd-opencli2rust/__tests__/custom-scaffold.test.ts create mode 100644 packages/xyd-opencli2rust/__tests__/docs.test.ts create mode 100644 packages/xyd-opencli2rust/__tests__/e2e/harness.ts create mode 100644 packages/xyd-opencli2rust/__tests__/e2e/openai.test.ts create mode 100644 packages/xyd-opencli2rust/__tests__/opencli2rust.test.ts create mode 100644 packages/xyd-opencli2rust/__tests__/utils.ts create mode 100644 packages/xyd-opencli2rust/biome.json create mode 100644 packages/xyd-opencli2rust/index.ts create mode 100644 packages/xyd-opencli2rust/package.json create mode 100644 packages/xyd-opencli2rust/src/cli.ts create mode 100644 packages/xyd-opencli2rust/src/command.ts create mode 100644 packages/xyd-opencli2rust/src/flags.ts create mode 100644 packages/xyd-opencli2rust/src/handler.ts create mode 100644 packages/xyd-opencli2rust/src/index.ts create mode 100644 packages/xyd-opencli2rust/src/main.ts create mode 100644 packages/xyd-opencli2rust/src/model.ts create mode 100644 packages/xyd-opencli2rust/src/naming.ts create mode 100644 packages/xyd-opencli2rust/src/project.ts create mode 100644 packages/xyd-opencli2rust/src/rslit.ts create mode 100644 packages/xyd-opencli2rust/src/runtime.ts create mode 100644 packages/xyd-opencli2rust/src/types.ts create mode 100644 packages/xyd-opencli2rust/src/write.ts create mode 100644 packages/xyd-opencli2rust/tsconfig.json create mode 100644 packages/xyd-opencli2rust/tsup.config.ts create mode 100644 packages/xyd-opencli2rust/vitest.config.ts diff --git a/.github/workflows/tests-opencli-pipeline.yml b/.github/workflows/tests-opencli-pipeline.yml index 2776003d8..764829803 100644 --- a/.github/workflows/tests-opencli-pipeline.yml +++ b/.github/workflows/tests-opencli-pipeline.yml @@ -1,16 +1,19 @@ name: tests:opencli-pipeline -# Verifies the OpenAPI → OpenCLI → Go pipeline end to end: +# Verifies the OpenAPI → OpenCLI → CLI pipeline end to end: # @xyd-js/opencli (core model) · @xyd-js/openapi2opencli (Stage A) -# · @xyd-js/opencli2go (Go generator) +# · @xyd-js/opencli2go (Go generator) · @xyd-js/opencli2rust (Rust generator) # # The root vitest config (tests:unit) already runs each package's offline unit -# tests, but it EXCLUDES `**/__tests__/e2e/**`. This job adds a Go toolchain so -# the gated layers can run where generated binaries actually execute (Linux): -# - O2G_GO_SMOKE=1 — a sample of generated Go projects `go build`/`go vet` -# - E2E_CLI=1 — assemble the whole CLI, run it against a recording server, -# diff the real HTTP requests against the committed fixtures -# (O2G_BUILD_DOCS / E2E_RECORD regenerate goldens and are deliberately NOT set.) +# tests, but it EXCLUDES `**/__tests__/e2e/**`. This job adds Go + Rust +# toolchains so the gated layers can run where generated binaries actually +# execute (Linux): +# - O2G_GO_SMOKE=1 — a sample of generated Go projects `go build`/`go vet` +# - O2R_CARGO_SMOKE=1 — generated Rust projects `cargo check` +# - E2E_CLI=1 — assemble the whole CLI, run it against a recording server, +# diff the real HTTP requests against the committed fixtures +# (O2G_BUILD_DOCS / O2R_BUILD_DOCS / E2E_RECORD regenerate goldens and are +# deliberately NOT set.) on: push: @@ -19,6 +22,7 @@ on: - 'packages/xyd-opencli/**' - 'packages/xyd-openapi2opencli/**' - 'packages/xyd-opencli2go/**' + - 'packages/xyd-opencli2rust/**' - '.github/workflows/tests-opencli-pipeline.yml' pull_request: branches: [ master, canary ] @@ -26,6 +30,7 @@ on: - 'packages/xyd-opencli/**' - 'packages/xyd-openapi2opencli/**' - 'packages/xyd-opencli2go/**' + - 'packages/xyd-opencli2rust/**' - '.github/workflows/tests-opencli-pipeline.yml' jobs: @@ -62,6 +67,22 @@ jobs: # The generated CLIs vendor urfave/cli v3; cache its module downloads. cache: false + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + # The generated Rust CLIs share one target dir so clap/reqwest/tokio compile + # once across the cargo-check smoke samples and the e2e CLI build. + - name: Setup cargo cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + /tmp/o2r-target + key: ${{ runner.os }}-o2r-cargo-${{ hashFiles('packages/xyd-opencli2rust/src/runtime.ts') }} + restore-keys: | + ${{ runner.os }}-o2r-cargo- + - name: Get pnpm store directory shell: bash run: | @@ -84,6 +105,7 @@ jobs: --filter "@xyd-js/opencli..." --filter "@xyd-js/openapi2opencli..." --filter "@xyd-js/opencli2go..." + --filter "@xyd-js/opencli2rust..." build - name: Test @xyd-js/opencli (core model + schema round-trip) @@ -112,3 +134,10 @@ jobs: env: O2G_GO_SMOKE: '1' E2E_CLI: '1' + + - name: Test @xyd-js/opencli2rust (regen guard + cargo check + e2e CLI) + run: pnpm --filter @xyd-js/opencli2rust ci:test + env: + O2R_CARGO_SMOKE: '1' + E2E_CLI: '1' + CARGO_TARGET_DIR: /tmp/o2r-target diff --git a/packages/xyd-opencli2rust/README.md b/packages/xyd-opencli2rust/README.md new file mode 100644 index 000000000..d8bdf40bd --- /dev/null +++ b/packages/xyd-opencli2rust/README.md @@ -0,0 +1,123 @@ +# @xyd-js/opencli2rust + +Generates a **buildable, functional Rust CLI** ([clap v4](https://github.com/clap-rs/clap), +async [reqwest](https://github.com/seanmonstar/reqwest)) from an OpenCLI document +([`@xyd-js/opencli`](../xyd-opencli)). It reads the `x-openapi` request binding (emitted by +[`@xyd-js/openapi2opencli`](../xyd-openapi2opencli)) so the generated commands make +**real API requests** — not stubs. The Rust sibling of +[`@xyd-js/opencli2go`](../xyd-opencli2go), plus **regen-safe custom-code seams** +(the Fern / Oxide-progenitor concept: keep hand-written logic alongside generated code). + +```ts +import { opencli2rust, writeProject } from '@xyd-js/opencli2rust'; + +const files = opencli2rust(opencliDoc, { binName: 'openai' }); // pure: ProjectFileMap +await writeProject(files, './out'); // full regen lifecycle +await writeProject(files, './out', { merge: true }); // + 3-way merge of hand-edits +``` + +## Output + +``` +Cargo.toml # skipIfExists — yours after first generation (add deps freely) +.gitignore # skipIfExists +src/main.rs # ~10-line wiring seam (custom::register + gen::cli::run) +src/gen/ # generator-owned — "DO NOT EDIT" (regenerated every run) + cli.rs # root clap tree, custom grafting, custom-first dispatch + cmd/.rs # one per top-level command: command() + run() + handlers + runtime/http.rs # Request / Error / path_escape (static) + runtime/mod.rs # Context (execute/execute_raw) + run_request hook threading + runtime/config.rs # generated: base URL + auth from x-openapi + runtime/overrides.rs # the CliOverrides trait (all methods defaulted) + runtime/custom.rs # the CustomCommands registry + clap grafting +src/custom/mod.rs # skipIfExists — YOURS: overrides + custom commands +``` + +## Custom code that survives regeneration + +Three seams, all optional (the untouched scaffold compiles and behaves like the stock CLI): + +**1. Hook/override every generated command** — implement `CliOverrides` methods in +`src/custom/mod.rs` (`before_request`, `transform_response`, `recover_error`, +`print_success`, `print_error`; all sync, all defaulted): + +```rust +impl CliOverrides for Custom { + fn print_success(&self, _cmd_path: &[String], value: &serde_json::Value) { + println!("{}", value["id"]); // e.g. print just the id + } +} +``` + +**2. Add fully custom commands** — `register()` grafts them anywhere in the tree; a command +registered on an **existing** path takes over that command's behavior (override semantics). +`Context` gives custom handlers the CLI's own base URL + auth: + +```rust +pub fn register(commands: &mut CustomCommands) { + commands.add(&["tools"], clap::Command::new("hello").about("Say hello"), |ctx, _m| async move { + let value = ctx.execute(/* runtime::Request … */).await?; + println!("{value}"); + Ok(()) + }); +} +``` + +**3. Edit generated files directly** — with `writeProject(files, out, { merge: true })`, +hand-edits to `src/gen/**` survive regeneration via the framework's 3-way merge +(`base` = previous pristine generation, `ours` = your file, `theirs` = new generation); +conflicts get git-style markers. File-level protection (`skipIfExists`, `.sdkignore`, +`.sdk/sdk.lock` stale-prune guard) comes from +[`@xyd-js/opensdk-framework`](../xyd-opensdk-framework)'s `writeProject`. + +## How it works + +A **templated emitter** (fern's CLI-generator pattern), not a Rust AST: + +- The clap `Command` tree + typed args are rendered as builder-method chains; positionals are + declared per leaf (clap requires it), flags follow the encoding: `String` / + `value_parser!(i64/f64)` / bool (`--x` and `--x=false`) / `ArgAction::Append` slices. +- Each leaf **handler is generated from `x-openapi`**: positional path params → + `runtime::path_escape` into a `format!` path, guarded query/header population, body flags → + a `serde_json::Map` (encodings: int/float/bool/array/nested-json with scalar fallback), then + `runtime::run_request(ctx, o, cmd_path, req)` — which threads every request through the + `CliOverrides` hooks. Printing happens ONLY in the trait (unlike the Go runtime), so + overrides fully control output. +- `config.rs` bakes the base URL (overridable via `_BASE_URL`) and `apply_auth` + (bearer / apiKey-header / apiKey-query / apiKey-cookie / basic), reading credentials from + the env var named in `x-openapi.security`. + +`opencli2rust` is pure (returns a `ProjectFileMap` with per-file write modes); `writeProject` +does the disk IO with the framework's full regen lifecycle. + +## Options + +`binName` (default `slug(info.title)`), `crateName` (default `crate_name(info.title)`), +`edition` (default `2021`), `baseURL` (default first `x-openapi.servers`). + +## Tests + +Golden multi-file fixtures under `__fixtures__//` (`input.json` OpenCLI → `output/` Rust +tree), the custom-scaffold regen-safety suite, per-method OpenAI fixtures (synced copies from +`xyd-opencli2go`, which owns recording), and the e2e harness (build the merged CLI, replay +every `recorded.json` invocation against a recording server, diff the requests): + +```bash +pnpm --filter @xyd-js/opencli2rust test # goldens + regen guard (no Rust needed) +REGEN=1 pnpm --filter @xyd-js/opencli2rust test # regenerate the output/ trees +O2R_CARGO_SMOKE=1 pnpm --filter @xyd-js/opencli2rust test # opt-in: cargo check generated projects +O2R_BUILD_DOCS=1 pnpm --filter @xyd-js/opencli2rust test # re-sync OpenAI fixtures from opencli2go +E2E_CLI=1 CARGO_TARGET_DIR=/tmp/o2r-target \ + pnpm --filter @xyd-js/opencli2rust exec vitest run __tests__/e2e # real-CLI request diff +``` + +## Known limits (v1) + +- Multipart bodies (`bodyStyle: 'multipart'`) and `file`-typed flags are read as strings and + sent as JSON — same gap as the Go generator. +- Per-operation `server` / `security` overrides in `x-openapi` are typed but not consumed + (global config wins) — same as the Go generator. +- A custom command registered on an existing path overrides its **behavior**; the generated + clap definition keeps handling parsing/help for that path (pick a new name for different + args). +- `acceptedValues` are not yet emitted as clap `possible_values`. diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/input.json new file mode 100644 index 000000000..0f3f66ae2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/input.json @@ -0,0 +1,73 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "admin-api-keys", + "commands": [ + { + "name": "create", + "description": "Create an organization admin API key", + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/admin_api_keys", + "operationId": "admin-api-keys-create", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/output.rs new file mode 100644 index 000000000..4ae502653 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/output.rs @@ -0,0 +1,63 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("admin-api-keys") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create an organization admin API key") + .arg( + Arg::new("name") + .long("name") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "admin-api-keys", "create"] => handle_organization_admin_api_keys_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_admin_api_keys_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/admin_api_keys".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/recorded.json new file mode 100644 index 000000000..786ab534b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__create/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "admin-api-keys", + "create", + "--name", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/admin_api_keys", + "query": [], + "bodyFields": [ + "name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/input.json new file mode 100644 index 000000000..931df879e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/input.json @@ -0,0 +1,61 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "admin-api-keys", + "commands": [ + { + "name": "delete", + "description": "Delete an organization admin API key", + "arguments": [ + { + "name": "key-id", + "required": true + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/admin_api_keys/{key_id}", + "operationId": "admin-api-keys-delete", + "params": [ + { + "in": "path", + "name": "key_id", + "from": "argument:key-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/output.rs new file mode 100644 index 000000000..dc9285059 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/output.rs @@ -0,0 +1,55 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("admin-api-keys") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete an organization admin API key") + .arg( + Arg::new("key-id") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "admin-api-keys", "delete"] => handle_organization_admin_api_keys_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_admin_api_keys_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let key_id = m.get_one::("key-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/admin_api_keys/{}", runtime::path_escape(key_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/recorded.json new file mode 100644 index 000000000..0da3e612c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__delete/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "organization", + "admin-api-keys", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/admin_api_keys/{key_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/input.json new file mode 100644 index 000000000..0201de83a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/input.json @@ -0,0 +1,97 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "admin-api-keys", + "commands": [ + { + "name": "list", + "description": "List organization API keys", + "options": [ + { + "name": "after", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "limit", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/admin_api_keys", + "operationId": "admin-api-keys-list", + "params": [ + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/output.rs new file mode 100644 index 000000000..fed39bdec --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/output.rs @@ -0,0 +1,73 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("admin-api-keys") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List organization API keys") + .arg( + Arg::new("after") + .long("after"), + ) + .arg( + Arg::new("order") + .long("order"), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "admin-api-keys", "list"] => handle_organization_admin_api_keys_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_admin_api_keys_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/admin_api_keys".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/recorded.json new file mode 100644 index 000000000..01ab11965 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "admin-api-keys", + "list" + ], + "request": { + "method": "get", + "path": "/organization/admin_api_keys", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/input.json new file mode 100644 index 000000000..0201de83a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/input.json @@ -0,0 +1,97 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "admin-api-keys", + "commands": [ + { + "name": "list", + "description": "List organization API keys", + "options": [ + { + "name": "after", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "limit", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/admin_api_keys", + "operationId": "admin-api-keys-list", + "params": [ + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/output.rs new file mode 100644 index 000000000..fed39bdec --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/output.rs @@ -0,0 +1,73 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("admin-api-keys") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List organization API keys") + .arg( + Arg::new("after") + .long("after"), + ) + .arg( + Arg::new("order") + .long("order"), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "admin-api-keys", "list"] => handle_organization_admin_api_keys_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_admin_api_keys_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/admin_api_keys".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/recorded.json new file mode 100644 index 000000000..01ab11965 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__admin-api-keys__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "admin-api-keys", + "list" + ], + "request": { + "method": "get", + "path": "/organization/admin_api_keys", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/input.json new file mode 100644 index 000000000..5b43d89de --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/input.json @@ -0,0 +1,254 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "audit-logs", + "commands": [ + { + "name": "list", + "description": "List user actions and configuration changes within this organization.", + "options": [ + { + "name": "effective-at", + "description": "Return only events whose `effective_at` (Unix seconds) is in this range.", + "group": "query", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "project-ids[]", + "description": "Return only events for these projects.", + "group": "query", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "event-types[]", + "description": "Return only events with a `type` in one of these values. For example, `project.created`. For all options, see the documentation for the [audit log object](/docs/api-reference/audit-logs/object).", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "api_key.created", + "api_key.updated", + "api_key.deleted", + "certificate.created", + "certificate.updated", + "certificate.deleted", + "certificates.activated", + "certificates.deactivated", + "checkpoint.permission.created", + "checkpoint.permission.deleted", + "external_key.registered", + "external_key.removed", + "group.created", + "group.updated", + "group.deleted", + "invite.sent", + "invite.accepted", + "invite.deleted", + "ip_allowlist.created", + "ip_allowlist.updated", + "ip_allowlist.deleted", + "ip_allowlist.config.activated", + "ip_allowlist.config.deactivated", + "login.succeeded", + "login.failed", + "logout.succeeded", + "logout.failed", + "organization.updated", + "project.created", + "project.updated", + "project.archived", + "project.deleted", + "rate_limit.updated", + "rate_limit.deleted", + "resource.deleted", + "tunnel.created", + "tunnel.updated", + "tunnel.deleted", + "role.created", + "role.updated", + "role.deleted", + "role.assignment.created", + "role.assignment.deleted", + "scim.enabled", + "scim.disabled", + "service_account.created", + "service_account.updated", + "service_account.deleted", + "user.added", + "user.updated", + "user.deleted" + ], + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "actor-ids[]", + "description": "Return only events performed by these actors. Can be a user ID, a service account ID, or an api key tracking ID.", + "group": "query", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "actor-emails[]", + "description": "Return only events performed by users with these emails.", + "group": "query", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "resource-ids[]", + "description": "Return only events performed on these targets. For example, a project ID updated.", + "group": "query", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/audit_logs", + "operationId": "list-audit-logs", + "params": [ + { + "in": "query", + "name": "effective_at", + "from": "option:effective-at" + }, + { + "in": "query", + "name": "project_ids[]", + "from": "option:project-ids[]" + }, + { + "in": "query", + "name": "event_types[]", + "from": "option:event-types[]" + }, + { + "in": "query", + "name": "actor_ids[]", + "from": "option:actor-ids[]" + }, + { + "in": "query", + "name": "actor_emails[]", + "from": "option:actor-emails[]" + }, + { + "in": "query", + "name": "resource_ids[]", + "from": "option:resource-ids[]" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/output.rs new file mode 100644 index 000000000..b045d85ec --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/output.rs @@ -0,0 +1,134 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("audit-logs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List user actions and configuration changes within this organization.") + .arg( + Arg::new("effective-at") + .long("effective-at") + .help("Return only events whose `effective_at` (Unix seconds) is in this range."), + ) + .arg( + Arg::new("project-ids[]") + .long("project-ids[]") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Return only events for these projects."), + ) + .arg( + Arg::new("event-types[]") + .long("event-types[]") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Return only events with a `type` in one of these values. For example, `project.created`. For all options, see the documentation for the [audit log object](/docs/api-reference/audit-logs/object)."), + ) + .arg( + Arg::new("actor-ids[]") + .long("actor-ids[]") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Return only events performed by these actors. Can be a user ID, a service account ID, or an api key tracking ID."), + ) + .arg( + Arg::new("actor-emails[]") + .long("actor-emails[]") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Return only events performed by users with these emails."), + ) + .arg( + Arg::new("resource-ids[]") + .long("resource-ids[]") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Return only events performed on these targets. For example, a project ID updated."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "audit-logs", "list"] => handle_organization_audit_logs_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_audit_logs_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/audit_logs".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("effective-at") { + query.push(("effective_at", v.clone())); + } + if let Some(vs) = m.get_many::("project-ids[]") { + query.push(("project_ids[]", vs.cloned().collect::>().join(","))); + } + if let Some(vs) = m.get_many::("event-types[]") { + query.push(("event_types[]", vs.cloned().collect::>().join(","))); + } + if let Some(vs) = m.get_many::("actor-ids[]") { + query.push(("actor_ids[]", vs.cloned().collect::>().join(","))); + } + if let Some(vs) = m.get_many::("actor-emails[]") { + query.push(("actor_emails[]", vs.cloned().collect::>().join(","))); + } + if let Some(vs) = m.get_many::("resource-ids[]") { + query.push(("resource_ids[]", vs.cloned().collect::>().join(","))); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/recorded.json new file mode 100644 index 000000000..2e04997b7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__audit-logs__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "audit-logs", + "list" + ], + "request": { + "method": "get", + "path": "/organization/audit_logs", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/input.json new file mode 100644 index 000000000..0b963830b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/input.json @@ -0,0 +1,90 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "certificates", + "commands": [ + { + "name": "create", + "description": "Upload a certificate to the organization. This does **not** automatically activate the certificate.\n\nOrganizations can upload up to 50 certificates.\n", + "options": [ + { + "name": "name", + "group": "body", + "description": "An optional name for the certificate", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "certificate", + "group": "body", + "required": true, + "description": "The certificate content in PEM format", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/certificates", + "operationId": "uploadCertificate", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string" + }, + { + "name": "certificate", + "from": "option:certificate", + "jsonPath": "certificate", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/output.rs new file mode 100644 index 000000000..343d4cc0e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/output.rs @@ -0,0 +1,72 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("certificates") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Upload a certificate to the organization. This does **not** automatically activate the certificate.\n\nOrganizations can upload up to 50 certificates.\n") + .arg( + Arg::new("name") + .long("name") + .help("An optional name for the certificate"), + ) + .arg( + Arg::new("certificate") + .long("certificate") + .help("The certificate content in PEM format") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "certificates", "create"] => handle_organization_certificates_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_certificates_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/certificates".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("certificate") { + body.insert("certificate".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/recorded.json new file mode 100644 index 000000000..5768b7205 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__activate/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "certificates", + "create", + "--certificate", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/certificates", + "query": [], + "bodyFields": [ + "certificate" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/input.json new file mode 100644 index 000000000..0b963830b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/input.json @@ -0,0 +1,90 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "certificates", + "commands": [ + { + "name": "create", + "description": "Upload a certificate to the organization. This does **not** automatically activate the certificate.\n\nOrganizations can upload up to 50 certificates.\n", + "options": [ + { + "name": "name", + "group": "body", + "description": "An optional name for the certificate", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "certificate", + "group": "body", + "required": true, + "description": "The certificate content in PEM format", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/certificates", + "operationId": "uploadCertificate", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string" + }, + { + "name": "certificate", + "from": "option:certificate", + "jsonPath": "certificate", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/output.rs new file mode 100644 index 000000000..343d4cc0e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/output.rs @@ -0,0 +1,72 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("certificates") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Upload a certificate to the organization. This does **not** automatically activate the certificate.\n\nOrganizations can upload up to 50 certificates.\n") + .arg( + Arg::new("name") + .long("name") + .help("An optional name for the certificate"), + ) + .arg( + Arg::new("certificate") + .long("certificate") + .help("The certificate content in PEM format") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "certificates", "create"] => handle_organization_certificates_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_certificates_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/certificates".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("certificate") { + body.insert("certificate".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/recorded.json new file mode 100644 index 000000000..5768b7205 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__create/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "certificates", + "create", + "--certificate", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/certificates", + "query": [], + "bodyFields": [ + "certificate" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/input.json new file mode 100644 index 000000000..0b963830b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/input.json @@ -0,0 +1,90 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "certificates", + "commands": [ + { + "name": "create", + "description": "Upload a certificate to the organization. This does **not** automatically activate the certificate.\n\nOrganizations can upload up to 50 certificates.\n", + "options": [ + { + "name": "name", + "group": "body", + "description": "An optional name for the certificate", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "certificate", + "group": "body", + "required": true, + "description": "The certificate content in PEM format", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/certificates", + "operationId": "uploadCertificate", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string" + }, + { + "name": "certificate", + "from": "option:certificate", + "jsonPath": "certificate", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/output.rs new file mode 100644 index 000000000..343d4cc0e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/output.rs @@ -0,0 +1,72 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("certificates") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Upload a certificate to the organization. This does **not** automatically activate the certificate.\n\nOrganizations can upload up to 50 certificates.\n") + .arg( + Arg::new("name") + .long("name") + .help("An optional name for the certificate"), + ) + .arg( + Arg::new("certificate") + .long("certificate") + .help("The certificate content in PEM format") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "certificates", "create"] => handle_organization_certificates_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_certificates_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/certificates".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("certificate") { + body.insert("certificate".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/recorded.json new file mode 100644 index 000000000..5768b7205 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__deactivate/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "certificates", + "create", + "--certificate", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/certificates", + "query": [], + "bodyFields": [ + "certificate" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/input.json new file mode 100644 index 000000000..d49229060 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/input.json @@ -0,0 +1,62 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "certificates", + "commands": [ + { + "name": "delete", + "description": "Delete a certificate from the organization.\n\nThe certificate must be inactive for the organization and all projects.\n", + "arguments": [ + { + "name": "certificate-id", + "required": true, + "description": "Unique ID of the certificate to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/certificates/{certificate_id}", + "operationId": "deleteCertificate", + "params": [ + { + "in": "path", + "name": "certificate_id", + "from": "argument:certificate-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/output.rs new file mode 100644 index 000000000..6be8700bf --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/output.rs @@ -0,0 +1,56 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("certificates") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a certificate from the organization.\n\nThe certificate must be inactive for the organization and all projects.\n") + .arg( + Arg::new("certificate-id") + .help("Unique ID of the certificate to delete.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "certificates", "delete"] => handle_organization_certificates_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_certificates_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let certificate_id = m.get_one::("certificate-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/certificates/{}", runtime::path_escape(certificate_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/recorded.json new file mode 100644 index 000000000..7eeef4f14 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__delete/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "organization", + "certificates", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/certificates/{certificate_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/input.json new file mode 100644 index 000000000..babef558b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/input.json @@ -0,0 +1,100 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "certificates", + "commands": [ + { + "name": "list", + "description": "List uploaded certificates for this organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/certificates", + "operationId": "listOrganizationCertificates", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/output.rs new file mode 100644 index 000000000..98d0f6c04 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("certificates") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List uploaded certificates for this organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "certificates", "list"] => handle_organization_certificates_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_certificates_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/certificates".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/recorded.json new file mode 100644 index 000000000..3e701dc79 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "certificates", + "list" + ], + "request": { + "method": "get", + "path": "/organization/certificates", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/input.json new file mode 100644 index 000000000..babef558b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/input.json @@ -0,0 +1,100 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "certificates", + "commands": [ + { + "name": "list", + "description": "List uploaded certificates for this organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/certificates", + "operationId": "listOrganizationCertificates", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/output.rs new file mode 100644 index 000000000..98d0f6c04 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("certificates") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List uploaded certificates for this organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "certificates", "list"] => handle_organization_certificates_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_certificates_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/certificates".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/recorded.json new file mode 100644 index 000000000..3e701dc79 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "certificates", + "list" + ], + "request": { + "method": "get", + "path": "/organization/certificates", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/input.json new file mode 100644 index 000000000..0b963830b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/input.json @@ -0,0 +1,90 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "certificates", + "commands": [ + { + "name": "create", + "description": "Upload a certificate to the organization. This does **not** automatically activate the certificate.\n\nOrganizations can upload up to 50 certificates.\n", + "options": [ + { + "name": "name", + "group": "body", + "description": "An optional name for the certificate", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "certificate", + "group": "body", + "required": true, + "description": "The certificate content in PEM format", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/certificates", + "operationId": "uploadCertificate", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string" + }, + { + "name": "certificate", + "from": "option:certificate", + "jsonPath": "certificate", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/output.rs new file mode 100644 index 000000000..343d4cc0e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/output.rs @@ -0,0 +1,72 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("certificates") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Upload a certificate to the organization. This does **not** automatically activate the certificate.\n\nOrganizations can upload up to 50 certificates.\n") + .arg( + Arg::new("name") + .long("name") + .help("An optional name for the certificate"), + ) + .arg( + Arg::new("certificate") + .long("certificate") + .help("The certificate content in PEM format") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "certificates", "create"] => handle_organization_certificates_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_certificates_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/certificates".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("certificate") { + body.insert("certificate".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/recorded.json new file mode 100644 index 000000000..5768b7205 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__certificates__update/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "certificates", + "create", + "--certificate", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/certificates", + "query": [], + "bodyFields": [ + "certificate" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/input.json new file mode 100644 index 000000000..8c06dfba3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/input.json @@ -0,0 +1,74 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "create", + "description": "Creates a new group in the organization.", + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "description": "Human readable name for the group.", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/groups", + "operationId": "create-group", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/output.rs new file mode 100644 index 000000000..d3817de3f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/output.rs @@ -0,0 +1,64 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a new group in the organization.") + .arg( + Arg::new("name") + .long("name") + .help("Human readable name for the group.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "create"] => handle_organization_groups_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/groups".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/recorded.json new file mode 100644 index 000000000..855f2dde1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__create/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "groups", + "create", + "--name", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/groups", + "query": [], + "bodyFields": [ + "name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/input.json new file mode 100644 index 000000000..42bda9dd8 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/input.json @@ -0,0 +1,62 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "delete", + "description": "Deletes a group from the organization.", + "arguments": [ + { + "name": "group-id", + "required": true, + "description": "The ID of the group to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/groups/{group_id}", + "operationId": "delete-group", + "params": [ + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/output.rs new file mode 100644 index 000000000..97b5ba50b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/output.rs @@ -0,0 +1,56 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Deletes a group from the organization.") + .arg( + Arg::new("group-id") + .help("The ID of the group to delete.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "delete"] => handle_organization_groups_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/groups/{}", runtime::path_escape(group_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/recorded.json new file mode 100644 index 000000000..2e4d30df9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__delete/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "organization", + "groups", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/groups/{group_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/input.json new file mode 100644 index 000000000..9ffd3f505 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/input.json @@ -0,0 +1,100 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "list", + "description": "Lists all groups in the organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of groups to be returned. Limit can range between 0 and 1000, and the default is 100.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is a group ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with group_abc, your subsequent call can include `after=group_abc` in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Specifies the sort order of the returned groups.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/groups", + "operationId": "list-groups", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/output.rs new file mode 100644 index 000000000..2a8d6ffce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists all groups in the organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of groups to be returned. Limit can range between 0 and 1000, and the default is 100.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is a group ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with group_abc, your subsequent call can include `after=group_abc` in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Specifies the sort order of the returned groups."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "list"] => handle_organization_groups_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/groups".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/recorded.json new file mode 100644 index 000000000..7d8139358 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "groups", + "list" + ], + "request": { + "method": "get", + "path": "/organization/groups", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/input.json new file mode 100644 index 000000000..9ffd3f505 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/input.json @@ -0,0 +1,100 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "list", + "description": "Lists all groups in the organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of groups to be returned. Limit can range between 0 and 1000, and the default is 100.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is a group ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with group_abc, your subsequent call can include `after=group_abc` in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Specifies the sort order of the returned groups.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/groups", + "operationId": "list-groups", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/output.rs new file mode 100644 index 000000000..2a8d6ffce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists all groups in the organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of groups to be returned. Limit can range between 0 and 1000, and the default is 100.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is a group ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with group_abc, your subsequent call can include `after=group_abc` in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Specifies the sort order of the returned groups."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "list"] => handle_organization_groups_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/groups".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/recorded.json new file mode 100644 index 000000000..7d8139358 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "groups", + "list" + ], + "request": { + "method": "get", + "path": "/organization/groups", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/input.json new file mode 100644 index 000000000..4654aced4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/input.json @@ -0,0 +1,94 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "create", + "description": "Assigns an organization role to a group within the organization.", + "arguments": [ + { + "name": "group-id", + "required": true, + "description": "The ID of the group that should receive the organization role." + } + ], + "options": [ + { + "name": "role-id", + "group": "body", + "required": true, + "description": "Identifier of the role to assign.", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/groups/{group_id}/roles", + "operationId": "assign-group-role", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "role_id", + "from": "option:role-id", + "jsonPath": "role_id", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/output.rs new file mode 100644 index 000000000..93496a855 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/output.rs @@ -0,0 +1,75 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Assigns an organization role to a group within the organization.") + .arg( + Arg::new("group-id") + .help("The ID of the group that should receive the organization role.") + .required(true), + ) + .arg( + Arg::new("role-id") + .long("role-id") + .help("Identifier of the role to assign.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "roles", "create"] => handle_organization_groups_roles_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_roles_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/groups/{}/roles", runtime::path_escape(group_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("role-id") { + body.insert("role_id".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/recorded.json new file mode 100644 index 000000000..2c60149f1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__create/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "organization", + "groups", + "roles", + "create", + "EXAMPLE", + "--role-id", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/groups/{group_id}/roles", + "query": [], + "bodyFields": [ + "role_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/input.json new file mode 100644 index 000000000..be419fce3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/input.json @@ -0,0 +1,78 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "delete", + "description": "Unassigns an organization role from a group within the organization.", + "arguments": [ + { + "name": "group-id", + "required": true, + "description": "The ID of the group to modify." + }, + { + "name": "role-id", + "required": true, + "description": "The ID of the organization role to remove from the group." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/groups/{group_id}/roles/{role_id}", + "operationId": "unassign-group-role", + "params": [ + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + }, + { + "in": "path", + "name": "role_id", + "from": "argument:role-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/output.rs new file mode 100644 index 000000000..c9fe34dc5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/output.rs @@ -0,0 +1,67 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Unassigns an organization role from a group within the organization.") + .arg( + Arg::new("group-id") + .help("The ID of the group to modify.") + .required(true), + ) + .arg( + Arg::new("role-id") + .help("The ID of the organization role to remove from the group.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "roles", "delete"] => handle_organization_groups_roles_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_roles_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let role_id = m.get_one::("role-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/groups/{}/roles/{}", runtime::path_escape(group_id), runtime::path_escape(role_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/recorded.json new file mode 100644 index 000000000..d8375f393 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__delete/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "organization", + "groups", + "roles", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/groups/{group_id}/roles/{role_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/input.json new file mode 100644 index 000000000..65dac0800 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "list", + "description": "Lists the organization roles assigned to a group within the organization.", + "arguments": [ + { + "name": "group-id", + "required": true, + "description": "The ID of the group whose organization role assignments you want to list." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of organization role assignments to return.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order for the returned organization roles.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/groups/{group_id}/roles", + "operationId": "list-group-role-assignments", + "params": [ + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/output.rs new file mode 100644 index 000000000..c9cbb2e08 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the organization roles assigned to a group within the organization.") + .arg( + Arg::new("group-id") + .help("The ID of the group whose organization role assignments you want to list.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of organization role assignments to return."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for the returned organization roles."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "roles", "list"] => handle_organization_groups_roles_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_roles_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/groups/{}/roles", runtime::path_escape(group_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/recorded.json new file mode 100644 index 000000000..79a7d8ddb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "groups", + "roles", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/groups/{group_id}/roles", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/input.json new file mode 100644 index 000000000..65dac0800 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "list", + "description": "Lists the organization roles assigned to a group within the organization.", + "arguments": [ + { + "name": "group-id", + "required": true, + "description": "The ID of the group whose organization role assignments you want to list." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of organization role assignments to return.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order for the returned organization roles.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/groups/{group_id}/roles", + "operationId": "list-group-role-assignments", + "params": [ + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/output.rs new file mode 100644 index 000000000..c9cbb2e08 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the organization roles assigned to a group within the organization.") + .arg( + Arg::new("group-id") + .help("The ID of the group whose organization role assignments you want to list.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of organization role assignments to return."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for the returned organization roles."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "roles", "list"] => handle_organization_groups_roles_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_roles_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/groups/{}/roles", runtime::path_escape(group_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/recorded.json new file mode 100644 index 000000000..79a7d8ddb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__roles__retrieve/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "groups", + "roles", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/groups/{group_id}/roles", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/input.json new file mode 100644 index 000000000..8c06dfba3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/input.json @@ -0,0 +1,74 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "create", + "description": "Creates a new group in the organization.", + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "description": "Human readable name for the group.", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/groups", + "operationId": "create-group", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/output.rs new file mode 100644 index 000000000..d3817de3f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/output.rs @@ -0,0 +1,64 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a new group in the organization.") + .arg( + Arg::new("name") + .long("name") + .help("Human readable name for the group.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "create"] => handle_organization_groups_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/groups".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/recorded.json new file mode 100644 index 000000000..855f2dde1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__update/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "groups", + "create", + "--name", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/groups", + "query": [], + "bodyFields": [ + "name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/input.json new file mode 100644 index 000000000..288fd0940 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/input.json @@ -0,0 +1,94 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "create", + "description": "Adds a user to a group.", + "arguments": [ + { + "name": "group-id", + "required": true, + "description": "The ID of the group to update." + } + ], + "options": [ + { + "name": "user-id", + "group": "body", + "required": true, + "description": "Identifier of the user to add to the group.", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/groups/{group_id}/users", + "operationId": "add-group-user", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "user_id", + "from": "option:user-id", + "jsonPath": "user_id", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/output.rs new file mode 100644 index 000000000..28c9237fe --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/output.rs @@ -0,0 +1,75 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Adds a user to a group.") + .arg( + Arg::new("group-id") + .help("The ID of the group to update.") + .required(true), + ) + .arg( + Arg::new("user-id") + .long("user-id") + .help("Identifier of the user to add to the group.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "users", "create"] => handle_organization_groups_users_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_users_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/groups/{}/users", runtime::path_escape(group_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("user-id") { + body.insert("user_id".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/recorded.json new file mode 100644 index 000000000..72985614f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__create/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "organization", + "groups", + "users", + "create", + "EXAMPLE", + "--user-id", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/groups/{group_id}/users", + "query": [], + "bodyFields": [ + "user_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/input.json new file mode 100644 index 000000000..6fc1e7954 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/input.json @@ -0,0 +1,78 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "delete", + "description": "Removes a user from a group.", + "arguments": [ + { + "name": "group-id", + "required": true, + "description": "The ID of the group to update." + }, + { + "name": "user-id", + "required": true, + "description": "The ID of the user to remove from the group." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/groups/{group_id}/users/{user_id}", + "operationId": "remove-group-user", + "params": [ + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + }, + { + "in": "path", + "name": "user_id", + "from": "argument:user-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/output.rs new file mode 100644 index 000000000..ee3aef5a4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/output.rs @@ -0,0 +1,67 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Removes a user from a group.") + .arg( + Arg::new("group-id") + .help("The ID of the group to update.") + .required(true), + ) + .arg( + Arg::new("user-id") + .help("The ID of the user to remove from the group.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "users", "delete"] => handle_organization_groups_users_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_users_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/groups/{}/users/{}", runtime::path_escape(group_id), runtime::path_escape(user_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/recorded.json new file mode 100644 index 000000000..dbe2085e9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__delete/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "organization", + "groups", + "users", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/groups/{group_id}/users/{user_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/input.json new file mode 100644 index 000000000..3b9096891 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "list", + "description": "Lists the users assigned to a group.", + "arguments": [ + { + "name": "group-id", + "required": true, + "description": "The ID of the group to inspect." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of users to be returned. Limit can range between 0 and 1000, and the default is 100.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. Provide the ID of the last user from the previous list response to retrieve the next page.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Specifies the sort order of users in the list.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/groups/{group_id}/users", + "operationId": "list-group-users", + "params": [ + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/output.rs new file mode 100644 index 000000000..0d29a24cd --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the users assigned to a group.") + .arg( + Arg::new("group-id") + .help("The ID of the group to inspect.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of users to be returned. Limit can range between 0 and 1000, and the default is 100.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. Provide the ID of the last user from the previous list response to retrieve the next page.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Specifies the sort order of users in the list."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "users", "list"] => handle_organization_groups_users_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_users_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/groups/{}/users", runtime::path_escape(group_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/recorded.json new file mode 100644 index 000000000..a6d6ca405 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "groups", + "users", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/groups/{group_id}/users", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/input.json new file mode 100644 index 000000000..3b9096891 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "list", + "description": "Lists the users assigned to a group.", + "arguments": [ + { + "name": "group-id", + "required": true, + "description": "The ID of the group to inspect." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of users to be returned. Limit can range between 0 and 1000, and the default is 100.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. Provide the ID of the last user from the previous list response to retrieve the next page.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Specifies the sort order of users in the list.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/groups/{group_id}/users", + "operationId": "list-group-users", + "params": [ + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/output.rs new file mode 100644 index 000000000..0d29a24cd --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the users assigned to a group.") + .arg( + Arg::new("group-id") + .help("The ID of the group to inspect.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of users to be returned. Limit can range between 0 and 1000, and the default is 100.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. Provide the ID of the last user from the previous list response to retrieve the next page.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Specifies the sort order of users in the list."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "groups", "users", "list"] => handle_organization_groups_users_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_groups_users_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/groups/{}/users", runtime::path_escape(group_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/recorded.json new file mode 100644 index 000000000..a6d6ca405 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__groups__users__retrieve/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "groups", + "users", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/groups/{group_id}/users", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/input.json new file mode 100644 index 000000000..5df504ea2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/input.json @@ -0,0 +1,115 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "invites", + "commands": [ + { + "name": "create", + "description": "Create an invite for a user to the organization. The invite must be accepted by the user before they have access to the organization.", + "options": [ + { + "name": "email", + "group": "body", + "required": true, + "description": "Send an email to this address", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "role", + "group": "body", + "required": true, + "description": "`owner` or `reader`", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "reader", + "owner" + ] + } + ] + }, + { + "name": "projects", + "group": "body", + "description": "An array of projects to which membership is granted at the same time the org invite is accepted. If omitted, the user will be invited to the default project for compatibility with legacy behavior.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/invites", + "operationId": "inviteUser", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "email", + "from": "option:email", + "jsonPath": "email", + "encoding": "string", + "required": true + }, + { + "name": "role", + "from": "option:role", + "jsonPath": "role", + "encoding": "string", + "required": true + }, + { + "name": "projects", + "from": "option:projects", + "jsonPath": "projects", + "encoding": "array" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/output.rs new file mode 100644 index 000000000..26ec1a2cb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/output.rs @@ -0,0 +1,83 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("invites") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create an invite for a user to the organization. The invite must be accepted by the user before they have access to the organization.") + .arg( + Arg::new("email") + .long("email") + .help("Send an email to this address") + .required(true), + ) + .arg( + Arg::new("role") + .long("role") + .help("`owner` or `reader`") + .required(true), + ) + .arg( + Arg::new("projects") + .long("projects") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("An array of projects to which membership is granted at the same time the org invite is accepted. If omitted, the user will be invited to the default project for compatibility with legacy behavior."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "invites", "create"] => handle_organization_invites_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_invites_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/invites".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("email") { + body.insert("email".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("role") { + body.insert("role".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("projects") { + body.insert("projects".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/recorded.json new file mode 100644 index 000000000..6b5c1fde6 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__create/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "organization", + "invites", + "create", + "--email", + "EXAMPLE", + "--role", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/invites", + "query": [], + "bodyFields": [ + "email", + "role" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/input.json new file mode 100644 index 000000000..739b4d09a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/input.json @@ -0,0 +1,62 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "invites", + "commands": [ + { + "name": "delete", + "description": "Delete an invite. If the invite has already been accepted, it cannot be deleted.", + "arguments": [ + { + "name": "invite-id", + "required": true, + "description": "The ID of the invite to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/invites/{invite_id}", + "operationId": "delete-invite", + "params": [ + { + "in": "path", + "name": "invite_id", + "from": "argument:invite-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/output.rs new file mode 100644 index 000000000..295ff97ca --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/output.rs @@ -0,0 +1,56 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("invites") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete an invite. If the invite has already been accepted, it cannot be deleted.") + .arg( + Arg::new("invite-id") + .help("The ID of the invite to delete.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "invites", "delete"] => handle_organization_invites_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_invites_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let invite_id = m.get_one::("invite-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/invites/{}", runtime::path_escape(invite_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/recorded.json new file mode 100644 index 000000000..36fee34c8 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__delete/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "organization", + "invites", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/invites/{invite_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/input.json new file mode 100644 index 000000000..f7d8daace --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/input.json @@ -0,0 +1,81 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "invites", + "commands": [ + { + "name": "list", + "description": "Returns a list of invites in the organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/invites", + "operationId": "list-invites", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/output.rs new file mode 100644 index 000000000..9a1960a15 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/output.rs @@ -0,0 +1,68 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("invites") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of invites in the organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "invites", "list"] => handle_organization_invites_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_invites_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/invites".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/recorded.json new file mode 100644 index 000000000..7588254c7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "invites", + "list" + ], + "request": { + "method": "get", + "path": "/organization/invites", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/input.json new file mode 100644 index 000000000..f7d8daace --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/input.json @@ -0,0 +1,81 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "invites", + "commands": [ + { + "name": "list", + "description": "Returns a list of invites in the organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/invites", + "operationId": "list-invites", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/output.rs new file mode 100644 index 000000000..9a1960a15 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/output.rs @@ -0,0 +1,68 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("invites") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of invites in the organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "invites", "list"] => handle_organization_invites_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_invites_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/invites".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/recorded.json new file mode 100644 index 000000000..7588254c7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__invites__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "invites", + "list" + ], + "request": { + "method": "get", + "path": "/organization/invites", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/input.json new file mode 100644 index 000000000..63caa4343 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/input.json @@ -0,0 +1,78 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "api-keys", + "commands": [ + { + "name": "delete", + "description": "Deletes an API key from the project.\n\nReturns confirmation of the key deletion, or an error if the key belonged to\na service account.\n", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + }, + { + "name": "api-key-id", + "required": true, + "description": "The ID of the API key." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/projects/{project_id}/api_keys/{api_key_id}", + "operationId": "delete-project-api-key", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "path", + "name": "api_key_id", + "from": "argument:api-key-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/output.rs new file mode 100644 index 000000000..1f2b4b268 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/output.rs @@ -0,0 +1,67 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("api-keys") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Deletes an API key from the project.\n\nReturns confirmation of the key deletion, or an error if the key belonged to\na service account.\n") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("api-key-id") + .help("The ID of the API key.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "api-keys", "delete"] => handle_organization_projects_api_keys_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_api_keys_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let api_key_id = m.get_one::("api-key-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/api_keys/{}", runtime::path_escape(project_id), runtime::path_escape(api_key_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/recorded.json new file mode 100644 index 000000000..a415e616f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__delete/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "organization", + "projects", + "api-keys", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/projects/{project_id}/api_keys/{api_key_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/input.json new file mode 100644 index 000000000..aa11f6688 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "api-keys", + "commands": [ + { + "name": "list", + "description": "Returns a list of API keys in the project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/api_keys", + "operationId": "list-project-api-keys", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/output.rs new file mode 100644 index 000000000..a6a07b59e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("api-keys") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of API keys in the project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "api-keys", "list"] => handle_organization_projects_api_keys_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_api_keys_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/api_keys", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/recorded.json new file mode 100644 index 000000000..03879fa44 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "api-keys", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/api_keys", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/input.json new file mode 100644 index 000000000..aa11f6688 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "api-keys", + "commands": [ + { + "name": "list", + "description": "Returns a list of API keys in the project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/api_keys", + "operationId": "list-project-api-keys", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/output.rs new file mode 100644 index 000000000..a6a07b59e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("api-keys") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of API keys in the project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "api-keys", "list"] => handle_organization_projects_api_keys_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_api_keys_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/api_keys", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/recorded.json new file mode 100644 index 000000000..03879fa44 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__api-keys__retrieve/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "api-keys", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/api_keys", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/input.json new file mode 100644 index 000000000..c4d64bb53 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/input.json @@ -0,0 +1,106 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "create", + "description": "Create a new project in the organization. Projects can be created and archived, but cannot be deleted.", + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "description": "The friendly name of the project, this name appears in reports.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "geography", + "group": "body", + "description": "Create the project with the specified data residency region. Your organization must have access to Data residency functionality in order to use. See [data residency controls](/docs/guides/your-data#data-residency-controls) to review the functionality and limitations of setting this field.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "external-key-id", + "group": "body", + "description": "External key ID to associate with the project.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/projects", + "operationId": "create-project", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + }, + { + "name": "geography", + "from": "option:geography", + "jsonPath": "geography", + "encoding": "json" + }, + { + "name": "external_key_id", + "from": "option:external-key-id", + "jsonPath": "external_key_id", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/output.rs new file mode 100644 index 000000000..db3ff352d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/output.rs @@ -0,0 +1,84 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new project in the organization. Projects can be created and archived, but cannot be deleted.") + .arg( + Arg::new("name") + .long("name") + .help("The friendly name of the project, this name appears in reports.") + .required(true), + ) + .arg( + Arg::new("geography") + .long("geography") + .help("Create the project with the specified data residency region. Your organization must have access to Data residency functionality in order to use. See [data residency controls](/docs/guides/your-data#data-residency-controls) to review the functionality and limitations of setting this field."), + ) + .arg( + Arg::new("external-key-id") + .long("external-key-id") + .help("External key ID to associate with the project."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "create"] => handle_organization_projects_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/projects".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("geography") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("geography".to_string(), value); + } + if let Some(v) = m.get_one::("external-key-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("external_key_id".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/recorded.json new file mode 100644 index 000000000..ad988ef80 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__archive/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "projects", + "create", + "--name", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/projects", + "query": [], + "bodyFields": [ + "name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/input.json new file mode 100644 index 000000000..b5a48f5bc --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "certificates", + "commands": [ + { + "name": "list", + "description": "List certificates for this project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/certificates", + "operationId": "listProjectCertificates", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/output.rs new file mode 100644 index 000000000..58b5156af --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("certificates") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List certificates for this project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "certificates", "list"] => handle_organization_projects_certificates_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_certificates_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/certificates", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/recorded.json new file mode 100644 index 000000000..37f225e91 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__certificates__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "certificates", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/certificates", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/input.json new file mode 100644 index 000000000..c4d64bb53 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/input.json @@ -0,0 +1,106 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "create", + "description": "Create a new project in the organization. Projects can be created and archived, but cannot be deleted.", + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "description": "The friendly name of the project, this name appears in reports.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "geography", + "group": "body", + "description": "Create the project with the specified data residency region. Your organization must have access to Data residency functionality in order to use. See [data residency controls](/docs/guides/your-data#data-residency-controls) to review the functionality and limitations of setting this field.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "external-key-id", + "group": "body", + "description": "External key ID to associate with the project.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/projects", + "operationId": "create-project", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + }, + { + "name": "geography", + "from": "option:geography", + "jsonPath": "geography", + "encoding": "json" + }, + { + "name": "external_key_id", + "from": "option:external-key-id", + "jsonPath": "external_key_id", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/output.rs new file mode 100644 index 000000000..db3ff352d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/output.rs @@ -0,0 +1,84 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new project in the organization. Projects can be created and archived, but cannot be deleted.") + .arg( + Arg::new("name") + .long("name") + .help("The friendly name of the project, this name appears in reports.") + .required(true), + ) + .arg( + Arg::new("geography") + .long("geography") + .help("Create the project with the specified data residency region. Your organization must have access to Data residency functionality in order to use. See [data residency controls](/docs/guides/your-data#data-residency-controls) to review the functionality and limitations of setting this field."), + ) + .arg( + Arg::new("external-key-id") + .long("external-key-id") + .help("External key ID to associate with the project."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "create"] => handle_organization_projects_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/projects".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("geography") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("geography".to_string(), value); + } + if let Some(v) = m.get_one::("external-key-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("external_key_id".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/recorded.json new file mode 100644 index 000000000..ad988ef80 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__create/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "projects", + "create", + "--name", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/projects", + "query": [], + "bodyFields": [ + "name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/input.json new file mode 100644 index 000000000..32aa3ebcf --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/input.json @@ -0,0 +1,112 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "create", + "description": "Grants a group access to a project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project to update." + } + ], + "options": [ + { + "name": "group-id", + "group": "body", + "required": true, + "description": "Identifier of the group to add to the project.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "role", + "group": "body", + "required": true, + "description": "Identifier of the project role to grant to the group.", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/projects/{project_id}/groups", + "operationId": "add-project-group", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "group_id", + "from": "option:group-id", + "jsonPath": "group_id", + "encoding": "string", + "required": true + }, + { + "name": "role", + "from": "option:role", + "jsonPath": "role", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/output.rs new file mode 100644 index 000000000..d4e8bb212 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/output.rs @@ -0,0 +1,84 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Grants a group access to a project.") + .arg( + Arg::new("project-id") + .help("The ID of the project to update.") + .required(true), + ) + .arg( + Arg::new("group-id") + .long("group-id") + .help("Identifier of the group to add to the project.") + .required(true), + ) + .arg( + Arg::new("role") + .long("role") + .help("Identifier of the project role to grant to the group.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "groups", "create"] => handle_organization_projects_groups_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_groups_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/groups", runtime::path_escape(project_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("group-id") { + body.insert("group_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("role") { + body.insert("role".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/recorded.json new file mode 100644 index 000000000..1a2b90a68 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__create/recorded.json @@ -0,0 +1,24 @@ +{ + "args": [ + "organization", + "projects", + "groups", + "create", + "EXAMPLE", + "--group-id", + "EXAMPLE", + "--role", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/projects/{project_id}/groups", + "query": [], + "bodyFields": [ + "group_id", + "role" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/input.json new file mode 100644 index 000000000..85e68e049 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/input.json @@ -0,0 +1,78 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "delete", + "description": "Revokes a group's access to a project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project to update." + }, + { + "name": "group-id", + "required": true, + "description": "The ID of the group to remove from the project." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/projects/{project_id}/groups/{group_id}", + "operationId": "remove-project-group", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "path", + "name": "group_id", + "from": "argument:group-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/output.rs new file mode 100644 index 000000000..31aaeb22e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/output.rs @@ -0,0 +1,67 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Revokes a group's access to a project.") + .arg( + Arg::new("project-id") + .help("The ID of the project to update.") + .required(true), + ) + .arg( + Arg::new("group-id") + .help("The ID of the group to remove from the project.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "groups", "delete"] => handle_organization_projects_groups_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_groups_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let group_id = m.get_one::("group-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/groups/{}", runtime::path_escape(project_id), runtime::path_escape(group_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/recorded.json new file mode 100644 index 000000000..7d748d1a1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__delete/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "organization", + "projects", + "groups", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/projects/{project_id}/groups/{group_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/input.json new file mode 100644 index 000000000..942802217 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "list", + "description": "Lists the groups that have access to a project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project to inspect." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of project groups to return. Defaults to 20.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "Cursor for pagination. Provide the ID of the last group from the previous response to fetch the next page.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order for the returned groups.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/groups", + "operationId": "list-project-groups", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/output.rs new file mode 100644 index 000000000..1d337013e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the groups that have access to a project.") + .arg( + Arg::new("project-id") + .help("The ID of the project to inspect.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of project groups to return. Defaults to 20."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Cursor for pagination. Provide the ID of the last group from the previous response to fetch the next page."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for the returned groups."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "groups", "list"] => handle_organization_projects_groups_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_groups_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/groups", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/recorded.json new file mode 100644 index 000000000..cc6d74429 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "groups", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/groups", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/input.json new file mode 100644 index 000000000..942802217 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "groups", + "commands": [ + { + "name": "list", + "description": "Lists the groups that have access to a project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project to inspect." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of project groups to return. Defaults to 20.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "Cursor for pagination. Provide the ID of the last group from the previous response to fetch the next page.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order for the returned groups.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/groups", + "operationId": "list-project-groups", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/output.rs new file mode 100644 index 000000000..1d337013e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("groups") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the groups that have access to a project.") + .arg( + Arg::new("project-id") + .help("The ID of the project to inspect.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of project groups to return. Defaults to 20."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Cursor for pagination. Provide the ID of the last group from the previous response to fetch the next page."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for the returned groups."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "groups", "list"] => handle_organization_projects_groups_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_groups_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/groups", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/recorded.json new file mode 100644 index 000000000..cc6d74429 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__groups__retrieve/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "groups", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/groups", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/input.json new file mode 100644 index 000000000..c38e7942c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/input.json @@ -0,0 +1,91 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "list", + "description": "Returns a list of projects.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "include-archived", + "description": "If `true` returns all projects including those that have been `archived`. Archived projects are not included by default.", + "group": "query" + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects", + "operationId": "list-projects", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "include_archived", + "from": "option:include-archived" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/output.rs new file mode 100644 index 000000000..c937a414b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/output.rs @@ -0,0 +1,80 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of projects.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("include-archived") + .long("include-archived") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("If `true` returns all projects including those that have been `archived`. Archived projects are not included by default."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "list"] => handle_organization_projects_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/projects".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("include-archived") { + query.push(("include_archived", v.to_string())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/recorded.json new file mode 100644 index 000000000..122f6842b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "projects", + "list" + ], + "request": { + "method": "get", + "path": "/organization/projects", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/input.json new file mode 100644 index 000000000..e7731c298 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/input.json @@ -0,0 +1,114 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "rate-limits", + "commands": [ + { + "name": "list", + "description": "Returns the rate limits per model for a project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. The default is 100.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, beginning with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/rate_limits", + "operationId": "list-project-rate-limits", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/output.rs new file mode 100644 index 000000000..d349db1f3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("rate-limits") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns the rate limits per model for a project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. The default is 100.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, beginning with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "rate-limits", "list"] => handle_organization_projects_rate_limits_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_rate_limits_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/rate_limits", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/recorded.json new file mode 100644 index 000000000..6a9423800 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__list-rate-limits/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "rate-limits", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/rate_limits", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/input.json new file mode 100644 index 000000000..7574ce0ce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/input.json @@ -0,0 +1,183 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "rate-limits", + "commands": [ + { + "name": "update", + "description": "Updates a project rate limit.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + }, + { + "name": "rate-limit-id", + "required": true, + "description": "The ID of the rate limit." + } + ], + "options": [ + { + "name": "max-requests-per-1-minute", + "group": "body", + "description": "The maximum requests per minute.", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "max-tokens-per-1-minute", + "group": "body", + "description": "The maximum tokens per minute.", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "max-images-per-1-minute", + "group": "body", + "description": "The maximum images per minute. Only relevant for certain models.", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "max-audio-megabytes-per-1-minute", + "group": "body", + "description": "The maximum audio megabytes per minute. Only relevant for certain models.", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "max-requests-per-1-day", + "group": "body", + "description": "The maximum requests per day. Only relevant for certain models.", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "batch-1-day-max-input-tokens", + "group": "body", + "description": "The maximum batch input tokens per day. Only relevant for certain models.", + "arguments": [ + { + "name": "integer" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/projects/{project_id}/rate_limits/{rate_limit_id}", + "operationId": "update-project-rate-limits", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "path", + "name": "rate_limit_id", + "from": "argument:rate-limit-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "max_requests_per_1_minute", + "from": "option:max-requests-per-1-minute", + "jsonPath": "max_requests_per_1_minute", + "encoding": "integer" + }, + { + "name": "max_tokens_per_1_minute", + "from": "option:max-tokens-per-1-minute", + "jsonPath": "max_tokens_per_1_minute", + "encoding": "integer" + }, + { + "name": "max_images_per_1_minute", + "from": "option:max-images-per-1-minute", + "jsonPath": "max_images_per_1_minute", + "encoding": "integer" + }, + { + "name": "max_audio_megabytes_per_1_minute", + "from": "option:max-audio-megabytes-per-1-minute", + "jsonPath": "max_audio_megabytes_per_1_minute", + "encoding": "integer" + }, + { + "name": "max_requests_per_1_day", + "from": "option:max-requests-per-1-day", + "jsonPath": "max_requests_per_1_day", + "encoding": "integer" + }, + { + "name": "batch_1_day_max_input_tokens", + "from": "option:batch-1-day-max-input-tokens", + "jsonPath": "batch_1_day_max_input_tokens", + "encoding": "integer" + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/output.rs new file mode 100644 index 000000000..a6c65f0a3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/output.rs @@ -0,0 +1,126 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("rate-limits") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("update") + .about("Updates a project rate limit.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("rate-limit-id") + .help("The ID of the rate limit.") + .required(true), + ) + .arg( + Arg::new("max-requests-per-1-minute") + .long("max-requests-per-1-minute") + .value_parser(clap::value_parser!(i64)) + .help("The maximum requests per minute."), + ) + .arg( + Arg::new("max-tokens-per-1-minute") + .long("max-tokens-per-1-minute") + .value_parser(clap::value_parser!(i64)) + .help("The maximum tokens per minute."), + ) + .arg( + Arg::new("max-images-per-1-minute") + .long("max-images-per-1-minute") + .value_parser(clap::value_parser!(i64)) + .help("The maximum images per minute. Only relevant for certain models."), + ) + .arg( + Arg::new("max-audio-megabytes-per-1-minute") + .long("max-audio-megabytes-per-1-minute") + .value_parser(clap::value_parser!(i64)) + .help("The maximum audio megabytes per minute. Only relevant for certain models."), + ) + .arg( + Arg::new("max-requests-per-1-day") + .long("max-requests-per-1-day") + .value_parser(clap::value_parser!(i64)) + .help("The maximum requests per day. Only relevant for certain models."), + ) + .arg( + Arg::new("batch-1-day-max-input-tokens") + .long("batch-1-day-max-input-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum batch input tokens per day. Only relevant for certain models."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "rate-limits", "update"] => handle_organization_projects_rate_limits_update(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_rate_limits_update( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let rate_limit_id = m.get_one::("rate-limit-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/rate_limits/{}", runtime::path_escape(project_id), runtime::path_escape(rate_limit_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("max-requests-per-1-minute") { + body.insert("max_requests_per_1_minute".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("max-tokens-per-1-minute") { + body.insert("max_tokens_per_1_minute".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("max-images-per-1-minute") { + body.insert("max_images_per_1_minute".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("max-audio-megabytes-per-1-minute") { + body.insert("max_audio_megabytes_per_1_minute".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("max-requests-per-1-day") { + body.insert("max_requests_per_1_day".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("batch-1-day-max-input-tokens") { + body.insert("batch_1_day_max_input_tokens".to_string(), serde_json::Value::from(*v)); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/recorded.json new file mode 100644 index 000000000..c0fb8c277 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__rate-limits__update-rate-limit/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "organization", + "projects", + "rate-limits", + "update", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/projects/{project_id}/rate_limits/{rate_limit_id}", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/input.json new file mode 100644 index 000000000..c38e7942c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/input.json @@ -0,0 +1,91 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "list", + "description": "Returns a list of projects.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "include-archived", + "description": "If `true` returns all projects including those that have been `archived`. Archived projects are not included by default.", + "group": "query" + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects", + "operationId": "list-projects", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "include_archived", + "from": "option:include-archived" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/output.rs new file mode 100644 index 000000000..c937a414b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/output.rs @@ -0,0 +1,80 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of projects.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("include-archived") + .long("include-archived") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("If `true` returns all projects including those that have been `archived`. Archived projects are not included by default."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "list"] => handle_organization_projects_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/projects".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("include-archived") { + query.push(("include_archived", v.to_string())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/recorded.json new file mode 100644 index 000000000..122f6842b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "projects", + "list" + ], + "request": { + "method": "get", + "path": "/organization/projects", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/input.json new file mode 100644 index 000000000..4f946c230 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/input.json @@ -0,0 +1,94 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "service-accounts", + "commands": [ + { + "name": "create", + "description": "Creates a new service account in the project. This also returns an unredacted API key for the service account.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "description": "The name of the service account being created.", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/projects/{project_id}/service_accounts", + "operationId": "create-project-service-account", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/output.rs new file mode 100644 index 000000000..8d739f5b9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/output.rs @@ -0,0 +1,75 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("service-accounts") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a new service account in the project. This also returns an unredacted API key for the service account.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("name") + .long("name") + .help("The name of the service account being created.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "service-accounts", "create"] => handle_organization_projects_service_accounts_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_service_accounts_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/service_accounts", runtime::path_escape(project_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/recorded.json new file mode 100644 index 000000000..a4fae917a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__create/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "organization", + "projects", + "service-accounts", + "create", + "EXAMPLE", + "--name", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/projects/{project_id}/service_accounts", + "query": [], + "bodyFields": [ + "name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/input.json new file mode 100644 index 000000000..2a2d2f82b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/input.json @@ -0,0 +1,78 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "service-accounts", + "commands": [ + { + "name": "delete", + "description": "Deletes a service account from the project.\n\nReturns confirmation of service account deletion, or an error if the project\nis archived (archived projects have no service accounts).\n", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + }, + { + "name": "service-account-id", + "required": true, + "description": "The ID of the service account." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/projects/{project_id}/service_accounts/{service_account_id}", + "operationId": "delete-project-service-account", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "path", + "name": "service_account_id", + "from": "argument:service-account-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/output.rs new file mode 100644 index 000000000..1beb124be --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/output.rs @@ -0,0 +1,67 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("service-accounts") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Deletes a service account from the project.\n\nReturns confirmation of service account deletion, or an error if the project\nis archived (archived projects have no service accounts).\n") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("service-account-id") + .help("The ID of the service account.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "service-accounts", "delete"] => handle_organization_projects_service_accounts_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_service_accounts_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let service_account_id = m.get_one::("service-account-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/service_accounts/{}", runtime::path_escape(project_id), runtime::path_escape(service_account_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/recorded.json new file mode 100644 index 000000000..d806de145 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__delete/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "organization", + "projects", + "service-accounts", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/projects/{project_id}/service_accounts/{service_account_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/input.json new file mode 100644 index 000000000..b1d4f0129 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "service-accounts", + "commands": [ + { + "name": "list", + "description": "Returns a list of service accounts in the project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/service_accounts", + "operationId": "list-project-service-accounts", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/output.rs new file mode 100644 index 000000000..9bea254c7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("service-accounts") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of service accounts in the project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "service-accounts", "list"] => handle_organization_projects_service_accounts_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_service_accounts_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/service_accounts", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/recorded.json new file mode 100644 index 000000000..42ff6879e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "service-accounts", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/service_accounts", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/input.json new file mode 100644 index 000000000..b1d4f0129 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "service-accounts", + "commands": [ + { + "name": "list", + "description": "Returns a list of service accounts in the project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/service_accounts", + "operationId": "list-project-service-accounts", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/output.rs new file mode 100644 index 000000000..9bea254c7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("service-accounts") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of service accounts in the project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "service-accounts", "list"] => handle_organization_projects_service_accounts_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_service_accounts_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/service_accounts", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/recorded.json new file mode 100644 index 000000000..42ff6879e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__retrieve/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "service-accounts", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/service_accounts", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/input.json new file mode 100644 index 000000000..4f946c230 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/input.json @@ -0,0 +1,94 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "service-accounts", + "commands": [ + { + "name": "create", + "description": "Creates a new service account in the project. This also returns an unredacted API key for the service account.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "description": "The name of the service account being created.", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/projects/{project_id}/service_accounts", + "operationId": "create-project-service-account", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/output.rs new file mode 100644 index 000000000..8d739f5b9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/output.rs @@ -0,0 +1,75 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("service-accounts") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a new service account in the project. This also returns an unredacted API key for the service account.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("name") + .long("name") + .help("The name of the service account being created.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "service-accounts", "create"] => handle_organization_projects_service_accounts_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_service_accounts_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/service_accounts", runtime::path_escape(project_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/recorded.json new file mode 100644 index 000000000..a4fae917a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__service-accounts__update/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "organization", + "projects", + "service-accounts", + "create", + "EXAMPLE", + "--name", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/projects/{project_id}/service_accounts", + "query": [], + "bodyFields": [ + "name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/input.json new file mode 100644 index 000000000..c4d64bb53 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/input.json @@ -0,0 +1,106 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "create", + "description": "Create a new project in the organization. Projects can be created and archived, but cannot be deleted.", + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "description": "The friendly name of the project, this name appears in reports.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "geography", + "group": "body", + "description": "Create the project with the specified data residency region. Your organization must have access to Data residency functionality in order to use. See [data residency controls](/docs/guides/your-data#data-residency-controls) to review the functionality and limitations of setting this field.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "external-key-id", + "group": "body", + "description": "External key ID to associate with the project.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/projects", + "operationId": "create-project", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + }, + { + "name": "geography", + "from": "option:geography", + "jsonPath": "geography", + "encoding": "json" + }, + { + "name": "external_key_id", + "from": "option:external-key-id", + "jsonPath": "external_key_id", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/output.rs new file mode 100644 index 000000000..db3ff352d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/output.rs @@ -0,0 +1,84 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new project in the organization. Projects can be created and archived, but cannot be deleted.") + .arg( + Arg::new("name") + .long("name") + .help("The friendly name of the project, this name appears in reports.") + .required(true), + ) + .arg( + Arg::new("geography") + .long("geography") + .help("Create the project with the specified data residency region. Your organization must have access to Data residency functionality in order to use. See [data residency controls](/docs/guides/your-data#data-residency-controls) to review the functionality and limitations of setting this field."), + ) + .arg( + Arg::new("external-key-id") + .long("external-key-id") + .help("External key ID to associate with the project."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "create"] => handle_organization_projects_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/projects".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("geography") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("geography".to_string(), value); + } + if let Some(v) = m.get_one::("external-key-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("external_key_id".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/recorded.json new file mode 100644 index 000000000..ad988ef80 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__update/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "organization", + "projects", + "create", + "--name", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/projects", + "query": [], + "bodyFields": [ + "name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/input.json new file mode 100644 index 000000000..784ec056a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/input.json @@ -0,0 +1,126 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "create", + "description": "Adds a user to the project. Users must already be members of the organization to be added to a project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "user-id", + "group": "body", + "description": "The ID of the user.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "email", + "group": "body", + "description": "Email of the user to add.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "role", + "group": "body", + "required": true, + "description": "`owner` or `member`", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/projects/{project_id}/users", + "operationId": "create-project-user", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "user_id", + "from": "option:user-id", + "jsonPath": "user_id", + "encoding": "json" + }, + { + "name": "email", + "from": "option:email", + "jsonPath": "email", + "encoding": "json" + }, + { + "name": "role", + "from": "option:role", + "jsonPath": "role", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/output.rs new file mode 100644 index 000000000..e2e360111 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/output.rs @@ -0,0 +1,95 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Adds a user to the project. Users must already be members of the organization to be added to a project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("user-id") + .long("user-id") + .help("The ID of the user."), + ) + .arg( + Arg::new("email") + .long("email") + .help("Email of the user to add."), + ) + .arg( + Arg::new("role") + .long("role") + .help("`owner` or `member`") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "users", "create"] => handle_organization_projects_users_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_users_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/users", runtime::path_escape(project_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("user-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("user_id".to_string(), value); + } + if let Some(v) = m.get_one::("email") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("email".to_string(), value); + } + if let Some(v) = m.get_one::("role") { + body.insert("role".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/recorded.json new file mode 100644 index 000000000..efd0a4645 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__create/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "organization", + "projects", + "users", + "create", + "EXAMPLE", + "--role", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/projects/{project_id}/users", + "query": [], + "bodyFields": [ + "role" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/input.json new file mode 100644 index 000000000..eab2b6a5b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/input.json @@ -0,0 +1,78 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "delete", + "description": "Deletes a user from the project.\n\nReturns confirmation of project user deletion, or an error if the project is\narchived (archived projects have no users).\n", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + }, + { + "name": "user-id", + "required": true, + "description": "The ID of the user." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/projects/{project_id}/users/{user_id}", + "operationId": "delete-project-user", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "path", + "name": "user_id", + "from": "argument:user-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/output.rs new file mode 100644 index 000000000..a98bbfc7d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/output.rs @@ -0,0 +1,67 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Deletes a user from the project.\n\nReturns confirmation of project user deletion, or an error if the project is\narchived (archived projects have no users).\n") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("user-id") + .help("The ID of the user.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "users", "delete"] => handle_organization_projects_users_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_users_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/users/{}", runtime::path_escape(project_id), runtime::path_escape(user_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/recorded.json new file mode 100644 index 000000000..f505cecec --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__delete/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "organization", + "projects", + "users", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/projects/{project_id}/users/{user_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/input.json new file mode 100644 index 000000000..f7e1dd35b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "list", + "description": "Returns a list of users in the project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/users", + "operationId": "list-project-users", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/output.rs new file mode 100644 index 000000000..4c48c1315 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of users in the project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "users", "list"] => handle_organization_projects_users_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_users_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/users", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/recorded.json new file mode 100644 index 000000000..a0a3bce8f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "users", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/users", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/input.json new file mode 100644 index 000000000..f7e1dd35b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "list", + "description": "Returns a list of users in the project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/projects/{project_id}/users", + "operationId": "list-project-users", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/output.rs new file mode 100644 index 000000000..4c48c1315 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of users in the project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "users", "list"] => handle_organization_projects_users_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_users_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/users", runtime::path_escape(project_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/recorded.json new file mode 100644 index 000000000..a0a3bce8f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__retrieve/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "projects", + "users", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/projects/{project_id}/users", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/input.json new file mode 100644 index 000000000..784ec056a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/input.json @@ -0,0 +1,126 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "projects", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "create", + "description": "Adds a user to the project. Users must already be members of the organization to be added to a project.", + "arguments": [ + { + "name": "project-id", + "required": true, + "description": "The ID of the project." + } + ], + "options": [ + { + "name": "user-id", + "group": "body", + "description": "The ID of the user.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "email", + "group": "body", + "description": "Email of the user to add.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "role", + "group": "body", + "required": true, + "description": "`owner` or `member`", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/projects/{project_id}/users", + "operationId": "create-project-user", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "project_id", + "from": "argument:project-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "user_id", + "from": "option:user-id", + "jsonPath": "user_id", + "encoding": "json" + }, + { + "name": "email", + "from": "option:email", + "jsonPath": "email", + "encoding": "json" + }, + { + "name": "role", + "from": "option:role", + "jsonPath": "role", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/output.rs new file mode 100644 index 000000000..e2e360111 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/output.rs @@ -0,0 +1,95 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Adds a user to the project. Users must already be members of the organization to be added to a project.") + .arg( + Arg::new("project-id") + .help("The ID of the project.") + .required(true), + ) + .arg( + Arg::new("user-id") + .long("user-id") + .help("The ID of the user."), + ) + .arg( + Arg::new("email") + .long("email") + .help("Email of the user to add."), + ) + .arg( + Arg::new("role") + .long("role") + .help("`owner` or `member`") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "projects", "users", "create"] => handle_organization_projects_users_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_projects_users_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let project_id = m.get_one::("project-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/projects/{}/users", runtime::path_escape(project_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("user-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("user_id".to_string(), value); + } + if let Some(v) = m.get_one::("email") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("email".to_string(), value); + } + if let Some(v) = m.get_one::("role") { + body.insert("role".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/recorded.json new file mode 100644 index 000000000..efd0a4645 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__projects__users__update/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "organization", + "projects", + "users", + "create", + "EXAMPLE", + "--role", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/projects/{project_id}/users", + "query": [], + "bodyFields": [ + "role" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/input.json new file mode 100644 index 000000000..67f5bce6f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/input.json @@ -0,0 +1,111 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "create", + "description": "Creates a custom role for the organization.", + "options": [ + { + "name": "role-name", + "group": "body", + "required": true, + "description": "Unique name for the role.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "permissions", + "group": "body", + "required": true, + "description": "Permissions to grant to the role.", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "description", + "group": "body", + "description": "Optional description of the role.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/roles", + "operationId": "create-role", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "role_name", + "from": "option:role-name", + "jsonPath": "role_name", + "encoding": "string", + "required": true + }, + { + "name": "permissions", + "from": "option:permissions", + "jsonPath": "permissions", + "encoding": "array", + "required": true + }, + { + "name": "description", + "from": "option:description", + "jsonPath": "description", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/output.rs new file mode 100644 index 000000000..c1c478c13 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/output.rs @@ -0,0 +1,85 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a custom role for the organization.") + .arg( + Arg::new("role-name") + .long("role-name") + .help("Unique name for the role.") + .required(true), + ) + .arg( + Arg::new("permissions") + .long("permissions") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Permissions to grant to the role.") + .required(true), + ) + .arg( + Arg::new("description") + .long("description") + .help("Optional description of the role."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "roles", "create"] => handle_organization_roles_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_roles_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/roles".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("role-name") { + body.insert("role_name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("permissions") { + body.insert("permissions".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("description") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("description".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/recorded.json new file mode 100644 index 000000000..12f4bbaeb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__create/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "organization", + "roles", + "create", + "--role-name", + "EXAMPLE", + "--permissions", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/roles", + "query": [], + "bodyFields": [ + "permissions", + "role_name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/input.json new file mode 100644 index 000000000..aa80f9655 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/input.json @@ -0,0 +1,62 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "delete", + "description": "Deletes a custom role from the organization.", + "arguments": [ + { + "name": "role-id", + "required": true, + "description": "The ID of the role to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/roles/{role_id}", + "operationId": "delete-role", + "params": [ + { + "in": "path", + "name": "role_id", + "from": "argument:role-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/output.rs new file mode 100644 index 000000000..eefaad186 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/output.rs @@ -0,0 +1,56 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Deletes a custom role from the organization.") + .arg( + Arg::new("role-id") + .help("The ID of the role to delete.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "roles", "delete"] => handle_organization_roles_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_roles_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let role_id = m.get_one::("role-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/roles/{}", runtime::path_escape(role_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/recorded.json new file mode 100644 index 000000000..e7a901e2e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__delete/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "organization", + "roles", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/roles/{role_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/input.json new file mode 100644 index 000000000..b07dc707d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/input.json @@ -0,0 +1,100 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "list", + "description": "Lists the roles configured for the organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of roles to return. Defaults to 1000.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing roles.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order for the returned roles.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/roles", + "operationId": "list-roles", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/output.rs new file mode 100644 index 000000000..56c2586bc --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the roles configured for the organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of roles to return. Defaults to 1000."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Cursor for pagination. Provide the value from the previous response's `next` field to continue listing roles."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for the returned roles."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "roles", "list"] => handle_organization_roles_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_roles_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/roles".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/recorded.json new file mode 100644 index 000000000..be7fbd6c0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "roles", + "list" + ], + "request": { + "method": "get", + "path": "/organization/roles", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/input.json new file mode 100644 index 000000000..b07dc707d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/input.json @@ -0,0 +1,100 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "list", + "description": "Lists the roles configured for the organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of roles to return. Defaults to 1000.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing roles.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order for the returned roles.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/roles", + "operationId": "list-roles", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/output.rs new file mode 100644 index 000000000..56c2586bc --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the roles configured for the organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of roles to return. Defaults to 1000."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Cursor for pagination. Provide the value from the previous response's `next` field to continue listing roles."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for the returned roles."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "roles", "list"] => handle_organization_roles_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_roles_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/roles".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/recorded.json new file mode 100644 index 000000000..be7fbd6c0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "roles", + "list" + ], + "request": { + "method": "get", + "path": "/organization/roles", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/input.json new file mode 100644 index 000000000..67f5bce6f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/input.json @@ -0,0 +1,111 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "create", + "description": "Creates a custom role for the organization.", + "options": [ + { + "name": "role-name", + "group": "body", + "required": true, + "description": "Unique name for the role.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "permissions", + "group": "body", + "required": true, + "description": "Permissions to grant to the role.", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "description", + "group": "body", + "description": "Optional description of the role.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/roles", + "operationId": "create-role", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "role_name", + "from": "option:role-name", + "jsonPath": "role_name", + "encoding": "string", + "required": true + }, + { + "name": "permissions", + "from": "option:permissions", + "jsonPath": "permissions", + "encoding": "array", + "required": true + }, + { + "name": "description", + "from": "option:description", + "jsonPath": "description", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/output.rs new file mode 100644 index 000000000..c1c478c13 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/output.rs @@ -0,0 +1,85 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a custom role for the organization.") + .arg( + Arg::new("role-name") + .long("role-name") + .help("Unique name for the role.") + .required(true), + ) + .arg( + Arg::new("permissions") + .long("permissions") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Permissions to grant to the role.") + .required(true), + ) + .arg( + Arg::new("description") + .long("description") + .help("Optional description of the role."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "roles", "create"] => handle_organization_roles_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_roles_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/roles".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("role-name") { + body.insert("role_name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("permissions") { + body.insert("permissions".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("description") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("description".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/recorded.json new file mode 100644 index 000000000..12f4bbaeb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__roles__update/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "organization", + "roles", + "create", + "--role-name", + "EXAMPLE", + "--permissions", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/roles", + "query": [], + "bodyFields": [ + "permissions", + "role_name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/input.json new file mode 100644 index 000000000..72a72a904 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/input.json @@ -0,0 +1,62 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "delete", + "description": "Deletes a user from the organization.", + "arguments": [ + { + "name": "user-id", + "required": true, + "description": "The ID of the user." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/users/{user_id}", + "operationId": "delete-user", + "params": [ + { + "in": "path", + "name": "user_id", + "from": "argument:user-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/output.rs new file mode 100644 index 000000000..ea6f33ba3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/output.rs @@ -0,0 +1,56 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Deletes a user from the organization.") + .arg( + Arg::new("user-id") + .help("The ID of the user.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "users", "delete"] => handle_organization_users_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_users_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/users/{}", runtime::path_escape(user_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/recorded.json new file mode 100644 index 000000000..7d9184b16 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__delete/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "organization", + "users", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/users/{user_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/input.json new file mode 100644 index 000000000..dbcfee7b7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "list", + "description": "Lists all of the users in the organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "emails", + "description": "Filter by the email address of users.", + "group": "query", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/users", + "operationId": "list-users", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "emails", + "from": "option:emails" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/output.rs new file mode 100644 index 000000000..c5fd89c9b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/output.rs @@ -0,0 +1,78 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists all of the users in the organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("emails") + .long("emails") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Filter by the email address of users."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "users", "list"] => handle_organization_users_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_users_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/users".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(vs) = m.get_many::("emails") { + query.push(("emails", vs.cloned().collect::>().join(","))); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/recorded.json new file mode 100644 index 000000000..a8b134b43 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "users", + "list" + ], + "request": { + "method": "get", + "path": "/organization/users", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/input.json new file mode 100644 index 000000000..dbcfee7b7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "list", + "description": "Lists all of the users in the organization.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "emails", + "description": "Filter by the email address of users.", + "group": "query", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/users", + "operationId": "list-users", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "emails", + "from": "option:emails" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/output.rs new file mode 100644 index 000000000..c5fd89c9b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/output.rs @@ -0,0 +1,78 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists all of the users in the organization.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("emails") + .long("emails") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Filter by the email address of users."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "users", "list"] => handle_organization_users_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_users_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/organization/users".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(vs) = m.get_many::("emails") { + query.push(("emails", vs.cloned().collect::>().join(","))); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/recorded.json new file mode 100644 index 000000000..a8b134b43 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "organization", + "users", + "list" + ], + "request": { + "method": "get", + "path": "/organization/users", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/input.json new file mode 100644 index 000000000..6f8020d9e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/input.json @@ -0,0 +1,94 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "create", + "description": "Assigns an organization role to a user within the organization.", + "arguments": [ + { + "name": "user-id", + "required": true, + "description": "The ID of the user that should receive the organization role." + } + ], + "options": [ + { + "name": "role-id", + "group": "body", + "required": true, + "description": "Identifier of the role to assign.", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/users/{user_id}/roles", + "operationId": "assign-user-role", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "user_id", + "from": "argument:user-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "role_id", + "from": "option:role-id", + "jsonPath": "role_id", + "encoding": "string", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/output.rs new file mode 100644 index 000000000..f42f4a526 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/output.rs @@ -0,0 +1,75 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Assigns an organization role to a user within the organization.") + .arg( + Arg::new("user-id") + .help("The ID of the user that should receive the organization role.") + .required(true), + ) + .arg( + Arg::new("role-id") + .long("role-id") + .help("Identifier of the role to assign.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "users", "roles", "create"] => handle_organization_users_roles_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_users_roles_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/users/{}/roles", runtime::path_escape(user_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("role-id") { + body.insert("role_id".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/recorded.json new file mode 100644 index 000000000..4820029a5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__create/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "organization", + "users", + "roles", + "create", + "EXAMPLE", + "--role-id", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/users/{user_id}/roles", + "query": [], + "bodyFields": [ + "role_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/input.json new file mode 100644 index 000000000..ddc2033e7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/input.json @@ -0,0 +1,78 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "delete", + "description": "Unassigns an organization role from a user within the organization.", + "arguments": [ + { + "name": "user-id", + "required": true, + "description": "The ID of the user to modify." + }, + { + "name": "role-id", + "required": true, + "description": "The ID of the organization role to remove from the user." + } + ], + "x-openapi": { + "method": "delete", + "path": "/organization/users/{user_id}/roles/{role_id}", + "operationId": "unassign-user-role", + "params": [ + { + "in": "path", + "name": "user_id", + "from": "argument:user-id", + "required": true + }, + { + "in": "path", + "name": "role_id", + "from": "argument:role-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/output.rs new file mode 100644 index 000000000..8fb100236 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/output.rs @@ -0,0 +1,67 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Unassigns an organization role from a user within the organization.") + .arg( + Arg::new("user-id") + .help("The ID of the user to modify.") + .required(true), + ) + .arg( + Arg::new("role-id") + .help("The ID of the organization role to remove from the user.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "users", "roles", "delete"] => handle_organization_users_roles_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_users_roles_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let role_id = m.get_one::("role-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/users/{}/roles/{}", runtime::path_escape(user_id), runtime::path_escape(role_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/recorded.json new file mode 100644 index 000000000..e5b1c45e3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__delete/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "organization", + "users", + "roles", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/organization/users/{user_id}/roles/{role_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/input.json new file mode 100644 index 000000000..2a38f044e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "list", + "description": "Lists the organization roles assigned to a user within the organization.", + "arguments": [ + { + "name": "user-id", + "required": true, + "description": "The ID of the user to inspect." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of organization role assignments to return.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order for the returned organization roles.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/users/{user_id}/roles", + "operationId": "list-user-role-assignments", + "params": [ + { + "in": "path", + "name": "user_id", + "from": "argument:user-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/output.rs new file mode 100644 index 000000000..ea496163b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the organization roles assigned to a user within the organization.") + .arg( + Arg::new("user-id") + .help("The ID of the user to inspect.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of organization role assignments to return."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for the returned organization roles."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "users", "roles", "list"] => handle_organization_users_roles_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_users_roles_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/users/{}/roles", runtime::path_escape(user_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/recorded.json new file mode 100644 index 000000000..2af800770 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "users", + "roles", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/users/{user_id}/roles", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/input.json new file mode 100644 index 000000000..2a38f044e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "roles", + "commands": [ + { + "name": "list", + "description": "Lists the organization roles assigned to a user within the organization.", + "arguments": [ + { + "name": "user-id", + "required": true, + "description": "The ID of the user to inspect." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of organization role assignments to return.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "after", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "order", + "description": "Sort order for the returned organization roles.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/organization/users/{user_id}/roles", + "operationId": "list-user-role-assignments", + "params": [ + { + "in": "path", + "name": "user_id", + "from": "argument:user-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/output.rs new file mode 100644 index 000000000..ea496163b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("roles") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the organization roles assigned to a user within the organization.") + .arg( + Arg::new("user-id") + .help("The ID of the user to inspect.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of organization role assignments to return."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for the returned organization roles."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "users", "roles", "list"] => handle_organization_users_roles_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_users_roles_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/users/{}/roles", runtime::path_escape(user_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/recorded.json new file mode 100644 index 000000000..2af800770 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__roles__retrieve/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "users", + "roles", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/organization/users/{user_id}/roles", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/input.json new file mode 100644 index 000000000..212ad8c76 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/input.json @@ -0,0 +1,135 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "organization", + "commands": [ + { + "name": "users", + "commands": [ + { + "name": "modify", + "description": "Modifies a user's role in the organization.", + "arguments": [ + { + "name": "user-id", + "required": true, + "description": "The ID of the user." + } + ], + "options": [ + { + "name": "role", + "group": "body", + "description": "`owner` or `reader`", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "role-id", + "group": "body", + "description": "Role ID to assign to the user.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "technical-level", + "group": "body", + "description": "Technical level metadata.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "developer-persona", + "group": "body", + "description": "Developer persona metadata.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/organization/users/{user_id}", + "operationId": "modify-user", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "user_id", + "from": "argument:user-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "role", + "from": "option:role", + "jsonPath": "role", + "encoding": "json" + }, + { + "name": "role_id", + "from": "option:role-id", + "jsonPath": "role_id", + "encoding": "json" + }, + { + "name": "technical_level", + "from": "option:technical-level", + "jsonPath": "technical_level", + "encoding": "json" + }, + { + "name": "developer_persona", + "from": "option:developer-persona", + "jsonPath": "developer_persona", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/output.rs new file mode 100644 index 000000000..88161b8b1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/output.rs @@ -0,0 +1,101 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("organization") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("users") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("modify") + .about("Modifies a user's role in the organization.") + .arg( + Arg::new("user-id") + .help("The ID of the user.") + .required(true), + ) + .arg( + Arg::new("role") + .long("role") + .help("`owner` or `reader`"), + ) + .arg( + Arg::new("role-id") + .long("role-id") + .help("Role ID to assign to the user."), + ) + .arg( + Arg::new("technical-level") + .long("technical-level") + .help("Technical level metadata."), + ) + .arg( + Arg::new("developer-persona") + .long("developer-persona") + .help("Developer persona metadata."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["organization", "users", "modify"] => handle_organization_users_modify(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_organization_users_modify( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let path = format!("/organization/users/{}", runtime::path_escape(user_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("role") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("role".to_string(), value); + } + if let Some(v) = m.get_one::("role-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("role_id".to_string(), value); + } + if let Some(v) = m.get_one::("technical-level") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("technical_level".to_string(), value); + } + if let Some(v) = m.get_one::("developer-persona") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("developer_persona".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/recorded.json new file mode 100644 index 000000000..6dd5b5f4d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/admin__organization__users__update/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "organization", + "users", + "modify", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/organization/users/{user_id}", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/input.json new file mode 100644 index 000000000..706b485be --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/input.json @@ -0,0 +1,186 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "audio", + "commands": [ + { + "name": "speech", + "commands": [ + { + "name": "create", + "description": "Generates audio from the input text.\n\nReturns the audio file content, or a stream of audio events.\n", + "options": [ + { + "name": "model", + "group": "body", + "required": true, + "description": "One of the available [TTS models](/docs/models#tts): `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, or `gpt-4o-mini-tts-2025-12-15`.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "input", + "group": "body", + "required": true, + "description": "The text to generate audio for. The maximum length is 4096 characters.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "instructions", + "group": "body", + "description": "Control the voice of your generated audio with additional instructions. Does not work with `tts-1` or `tts-1-hd`.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "voice", + "group": "body", + "required": true, + "description": "The voice to use when generating the audio. Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ \"id\": \"voice_1234\" }`. Previews of the voices are available in the [Text to speech guide](/docs/guides/text-to-speech#voice-options).", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "response-format", + "group": "body", + "description": "The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "mp3", + "opus", + "aac", + "flac", + "wav", + "pcm" + ] + } + ] + }, + { + "name": "speed", + "group": "body", + "description": "The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default.", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "stream-format", + "group": "body", + "description": "The format to stream the audio in. Supported formats are `sse` and `audio`. `sse` is not supported for `tts-1` or `tts-1-hd`.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "sse", + "audio" + ] + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/audio/speech", + "operationId": "createSpeech", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "input", + "from": "option:input", + "jsonPath": "input", + "encoding": "string", + "required": true + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "string" + }, + { + "name": "voice", + "from": "option:voice", + "jsonPath": "voice", + "encoding": "json", + "required": true + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "string" + }, + { + "name": "speed", + "from": "option:speed", + "jsonPath": "speed", + "encoding": "number" + }, + { + "name": "stream_format", + "from": "option:stream-format", + "jsonPath": "stream_format", + "encoding": "string" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/output.rs new file mode 100644 index 000000000..172ddd1cd --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/output.rs @@ -0,0 +1,119 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("audio") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("speech") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Generates audio from the input text.\n\nReturns the audio file content, or a stream of audio events.\n") + .arg( + Arg::new("model") + .long("model") + .help("One of the available [TTS models](/docs/models#tts): `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, or `gpt-4o-mini-tts-2025-12-15`.\n") + .required(true), + ) + .arg( + Arg::new("input") + .long("input") + .help("The text to generate audio for. The maximum length is 4096 characters.") + .required(true), + ) + .arg( + Arg::new("instructions") + .long("instructions") + .help("Control the voice of your generated audio with additional instructions. Does not work with `tts-1` or `tts-1-hd`."), + ) + .arg( + Arg::new("voice") + .long("voice") + .help("The voice to use when generating the audio. Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ \"id\": \"voice_1234\" }`. Previews of the voices are available in the [Text to speech guide](/docs/guides/text-to-speech#voice-options).") + .required(true), + ) + .arg( + Arg::new("response-format") + .long("response-format") + .help("The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`."), + ) + .arg( + Arg::new("speed") + .long("speed") + .value_parser(clap::value_parser!(f64)) + .help("The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default."), + ) + .arg( + Arg::new("stream-format") + .long("stream-format") + .help("The format to stream the audio in. Supported formats are `sse` and `audio`. `sse` is not supported for `tts-1` or `tts-1-hd`."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["audio", "speech", "create"] => handle_audio_speech_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_audio_speech_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/audio/speech".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("input") { + body.insert("input".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("instructions") { + body.insert("instructions".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("voice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("voice".to_string(), value); + } + if let Some(v) = m.get_one::("response-format") { + body.insert("response_format".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("speed") { + body.insert("speed".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("stream-format") { + body.insert("stream_format".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/recorded.json new file mode 100644 index 000000000..136af1622 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__speech__create/recorded.json @@ -0,0 +1,25 @@ +{ + "args": [ + "audio", + "speech", + "create", + "--model", + "EXAMPLE", + "--input", + "EXAMPLE", + "--voice", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/audio/speech", + "query": [], + "bodyFields": [ + "input", + "model", + "voice" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/input.json new file mode 100644 index 000000000..79f8b1133 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/input.json @@ -0,0 +1,277 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "audio", + "commands": [ + { + "name": "transcriptions", + "commands": [ + { + "name": "create", + "description": "Transcribes audio into the input language.\n\nReturns a transcription object in `json`, `diarized_json`, or `verbose_json`\nformat, or a stream of transcript events.\n", + "options": [ + { + "name": "file", + "group": "body", + "required": true, + "description": "The audio file object (not file name) to transcribe, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.\n", + "arguments": [ + { + "name": "path" + } + ] + }, + { + "name": "model", + "group": "body", + "required": true, + "description": "ID of the model to use. The options are `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` (which is powered by our open source Whisper V2 model), and `gpt-4o-transcribe-diarize`.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "language", + "group": "body", + "description": "The language of the input audio. Supplying the input language in [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) format will improve accuracy and latency.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "prompt", + "group": "body", + "description": "An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text#prompting) should match the audio language. This field is not supported when using `gpt-4o-transcribe-diarize`.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "response-format", + "group": "body", + "description": "The format of the output, in one of these options: `json`, `text`, `srt`, `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and `gpt-4o-mini-transcribe`, the only supported format is `json`. For `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and `diarized_json`, with `diarized_json` required to receive speaker annotations.\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "json", + "text", + "srt", + "verbose_json", + "vtt", + "diarized_json" + ] + } + ] + }, + { + "name": "temperature", + "group": "body", + "description": "The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "include", + "group": "body", + "description": "Additional information to include in the transcription response.\n`logprobs` will return the log probabilities of the tokens in the\nresponse to understand the model's confidence in the transcription.\n`logprobs` only works with response_format set to `json` and only with\nthe models `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is not supported when using `gpt-4o-transcribe-diarize`.\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "logprobs" + ], + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "timestamp-granularities", + "group": "body", + "description": "The timestamp granularities to populate for this transcription. `response_format` must be set `verbose_json` to use timestamp granularities. Either or both of these options are supported: `word`, or `segment`. Note: There is no additional latency for segment timestamps, but generating word timestamps incurs additional latency.\nThis option is not available for `gpt-4o-transcribe-diarize`.\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "word", + "segment" + ], + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "stream", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "chunking-strategy", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "known-speaker-names", + "group": "body", + "description": "Optional list of speaker names that correspond to the audio samples provided in `known_speaker_references[]`. Each entry should be a short identifier (for example `customer` or `agent`). Up to 4 speakers are supported.\n", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "known-speaker-references", + "group": "body", + "description": "Optional list of audio samples (as [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)) that contain known speaker references matching `known_speaker_names[]`. Each sample must be between 2 and 10 seconds, and can use any of the same input audio formats supported by `file`.\n", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/audio/transcriptions", + "operationId": "createTranscription", + "contentType": "multipart/form-data", + "body": { + "style": "multipart", + "contentType": "multipart/form-data", + "properties": [ + { + "name": "file", + "from": "option:file", + "jsonPath": "file", + "encoding": "file", + "required": true + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "language", + "from": "option:language", + "jsonPath": "language", + "encoding": "string" + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "string" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "string" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "number" + }, + { + "name": "include", + "from": "option:include", + "jsonPath": "include", + "encoding": "array" + }, + { + "name": "timestamp_granularities", + "from": "option:timestamp-granularities", + "jsonPath": "timestamp_granularities", + "encoding": "array" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "json" + }, + { + "name": "chunking_strategy", + "from": "option:chunking-strategy", + "jsonPath": "chunking_strategy", + "encoding": "json" + }, + { + "name": "known_speaker_names", + "from": "option:known-speaker-names", + "jsonPath": "known_speaker_names", + "encoding": "array" + }, + { + "name": "known_speaker_references", + "from": "option:known-speaker-references", + "jsonPath": "known_speaker_references", + "encoding": "array" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/output.rs new file mode 100644 index 000000000..b07aed3ce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/output.rs @@ -0,0 +1,166 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("audio") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("transcriptions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Transcribes audio into the input language.\n\nReturns a transcription object in `json`, `diarized_json`, or `verbose_json`\nformat, or a stream of transcript events.\n") + .arg( + Arg::new("file") + .long("file") + .help("The audio file object (not file name) to transcribe, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.\n") + .required(true), + ) + .arg( + Arg::new("model") + .long("model") + .help("ID of the model to use. The options are `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` (which is powered by our open source Whisper V2 model), and `gpt-4o-transcribe-diarize`.\n") + .required(true), + ) + .arg( + Arg::new("language") + .long("language") + .help("The language of the input audio. Supplying the input language in [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) format will improve accuracy and latency.\n"), + ) + .arg( + Arg::new("prompt") + .long("prompt") + .help("An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text#prompting) should match the audio language. This field is not supported when using `gpt-4o-transcribe-diarize`.\n"), + ) + .arg( + Arg::new("response-format") + .long("response-format") + .help("The format of the output, in one of these options: `json`, `text`, `srt`, `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and `gpt-4o-mini-transcribe`, the only supported format is `json`. For `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and `diarized_json`, with `diarized_json` required to receive speaker annotations.\n"), + ) + .arg( + Arg::new("temperature") + .long("temperature") + .value_parser(clap::value_parser!(f64)) + .help("The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit.\n"), + ) + .arg( + Arg::new("include") + .long("include") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Additional information to include in the transcription response.\n`logprobs` will return the log probabilities of the tokens in the\nresponse to understand the model's confidence in the transcription.\n`logprobs` only works with response_format set to `json` and only with\nthe models `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is not supported when using `gpt-4o-transcribe-diarize`.\n"), + ) + .arg( + Arg::new("timestamp-granularities") + .long("timestamp-granularities") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("The timestamp granularities to populate for this transcription. `response_format` must be set `verbose_json` to use timestamp granularities. Either or both of these options are supported: `word`, or `segment`. Note: There is no additional latency for segment timestamps, but generating word timestamps incurs additional latency.\nThis option is not available for `gpt-4o-transcribe-diarize`.\n"), + ) + .arg( + Arg::new("stream") + .long("stream"), + ) + .arg( + Arg::new("chunking-strategy") + .long("chunking-strategy"), + ) + .arg( + Arg::new("known-speaker-names") + .long("known-speaker-names") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Optional list of speaker names that correspond to the audio samples provided in `known_speaker_references[]`. Each entry should be a short identifier (for example `customer` or `agent`). Up to 4 speakers are supported.\n"), + ) + .arg( + Arg::new("known-speaker-references") + .long("known-speaker-references") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Optional list of audio samples (as [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)) that contain known speaker references matching `known_speaker_names[]`. Each sample must be between 2 and 10 seconds, and can use any of the same input audio formats supported by `file`.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["audio", "transcriptions", "create"] => handle_audio_transcriptions_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_audio_transcriptions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/audio/transcriptions".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("file") { + body.insert("file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("language") { + body.insert("language".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("prompt") { + body.insert("prompt".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("response-format") { + body.insert("response_format".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("temperature") { + body.insert("temperature".to_string(), serde_json::Value::from(*v)); + } + if let Some(vs) = m.get_many::("include") { + body.insert("include".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(vs) = m.get_many::("timestamp-granularities") { + body.insert("timestamp_granularities".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("stream") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream".to_string(), value); + } + if let Some(v) = m.get_one::("chunking-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chunking_strategy".to_string(), value); + } + if let Some(vs) = m.get_many::("known-speaker-names") { + body.insert("known_speaker_names".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(vs) = m.get_many::("known-speaker-references") { + body.insert("known_speaker_references".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/recorded.json new file mode 100644 index 000000000..5d69eb6c6 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__transcriptions__create/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "audio", + "transcriptions", + "create", + "--file", + "EXAMPLE", + "--model", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/audio/transcriptions", + "query": [], + "bodyFields": [ + "file", + "model" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/input.json new file mode 100644 index 000000000..1cd203760 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/input.json @@ -0,0 +1,147 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "audio", + "commands": [ + { + "name": "translations", + "commands": [ + { + "name": "create", + "description": "Translates audio into English.", + "options": [ + { + "name": "file", + "group": "body", + "required": true, + "description": "The audio file object (not file name) translate, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.\n", + "arguments": [ + { + "name": "path" + } + ] + }, + { + "name": "model", + "group": "body", + "required": true, + "description": "ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) is currently available.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "description": "An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text#prompting) should be in English.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "response-format", + "group": "body", + "description": "The format of the output, in one of these options: `json`, `text`, `srt`, `verbose_json`, or `vtt`.\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "json", + "text", + "srt", + "verbose_json", + "vtt" + ] + } + ] + }, + { + "name": "temperature", + "group": "body", + "description": "The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit.\n", + "arguments": [ + { + "name": "number" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/audio/translations", + "operationId": "createTranslation", + "contentType": "multipart/form-data", + "body": { + "style": "multipart", + "contentType": "multipart/form-data", + "properties": [ + { + "name": "file", + "from": "option:file", + "jsonPath": "file", + "encoding": "file", + "required": true + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "string" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "string" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "number" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/output.rs new file mode 100644 index 000000000..d5649b1f9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/output.rs @@ -0,0 +1,100 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("audio") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("translations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Translates audio into English.") + .arg( + Arg::new("file") + .long("file") + .help("The audio file object (not file name) translate, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.\n") + .required(true), + ) + .arg( + Arg::new("model") + .long("model") + .help("ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) is currently available.\n") + .required(true), + ) + .arg( + Arg::new("prompt") + .long("prompt") + .help("An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text#prompting) should be in English.\n"), + ) + .arg( + Arg::new("response-format") + .long("response-format") + .help("The format of the output, in one of these options: `json`, `text`, `srt`, `verbose_json`, or `vtt`.\n"), + ) + .arg( + Arg::new("temperature") + .long("temperature") + .value_parser(clap::value_parser!(f64)) + .help("The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["audio", "translations", "create"] => handle_audio_translations_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_audio_translations_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/audio/translations".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("file") { + body.insert("file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + body.insert("prompt".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("response-format") { + body.insert("response_format".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("temperature") { + body.insert("temperature".to_string(), serde_json::Value::from(*v)); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/recorded.json new file mode 100644 index 000000000..429a53468 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/audio__translations__create/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "audio", + "translations", + "create", + "--file", + "EXAMPLE", + "--model", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/audio/translations", + "query": [], + "bodyFields": [ + "file", + "model" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/input.json new file mode 100644 index 000000000..59c5171c7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/input.json @@ -0,0 +1,149 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "batches", + "commands": [ + { + "name": "create", + "description": "Creates and executes a batch from an uploaded file of requests", + "options": [ + { + "name": "input-file-id", + "group": "body", + "required": true, + "description": "The ID of an uploaded file that contains requests for the new batch.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour input file must be formatted as a [JSONL file](/docs/api-reference/batch/request-input), and must be uploaded with the purpose `batch`. The file can contain up to 50,000 requests, and can be up to 200 MB in size.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "endpoint", + "group": "body", + "required": true, + "description": "The endpoint to be used for all requests in the batch. Currently `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`, `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, and `/v1/videos` are supported. Note that `/v1/embeddings` batches are also restricted to a maximum of 50,000 embedding inputs across all requests in the batch.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "/v1/responses", + "/v1/chat/completions", + "/v1/embeddings", + "/v1/completions", + "/v1/moderations", + "/v1/images/generations", + "/v1/images/edits", + "/v1/videos" + ] + } + ] + }, + { + "name": "completion-window", + "group": "body", + "required": true, + "description": "The time frame within which the batch should be processed. Currently only `24h` is supported.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "24h" + ] + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "output-expires-after", + "group": "body", + "description": "The expiration policy for the output and/or error file that are generated for a batch.", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/batches", + "operationId": "createBatch", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "input_file_id", + "from": "option:input-file-id", + "jsonPath": "input_file_id", + "encoding": "string", + "required": true + }, + { + "name": "endpoint", + "from": "option:endpoint", + "jsonPath": "endpoint", + "encoding": "string", + "required": true + }, + { + "name": "completion_window", + "from": "option:completion-window", + "jsonPath": "completion_window", + "encoding": "string", + "required": true + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "output_expires_after", + "from": "option:output-expires-after", + "jsonPath": "output_expires_after", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/output.rs new file mode 100644 index 000000000..d28603120 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/output.rs @@ -0,0 +1,96 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("batches") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates and executes a batch from an uploaded file of requests") + .arg( + Arg::new("input-file-id") + .long("input-file-id") + .help("The ID of an uploaded file that contains requests for the new batch.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour input file must be formatted as a [JSONL file](/docs/api-reference/batch/request-input), and must be uploaded with the purpose `batch`. The file can contain up to 50,000 requests, and can be up to 200 MB in size.\n") + .required(true), + ) + .arg( + Arg::new("endpoint") + .long("endpoint") + .help("The endpoint to be used for all requests in the batch. Currently `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`, `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, and `/v1/videos` are supported. Note that `/v1/embeddings` batches are also restricted to a maximum of 50,000 embedding inputs across all requests in the batch.") + .required(true), + ) + .arg( + Arg::new("completion-window") + .long("completion-window") + .help("The time frame within which the batch should be processed. Currently only `24h` is supported.") + .required(true), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("output-expires-after") + .long("output-expires-after") + .help("The expiration policy for the output and/or error file that are generated for a batch."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["batches", "create"] => handle_batches_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_batches_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/batches".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("input-file-id") { + body.insert("input_file_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("endpoint") { + body.insert("endpoint".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("completion-window") { + body.insert("completion_window".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("output-expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("output_expires_after".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/recorded.json new file mode 100644 index 000000000..54fcf888d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__cancel/recorded.json @@ -0,0 +1,24 @@ +{ + "args": [ + "batches", + "create", + "--input-file-id", + "EXAMPLE", + "--endpoint", + "EXAMPLE", + "--completion-window", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/batches", + "query": [], + "bodyFields": [ + "completion_window", + "endpoint", + "input_file_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/input.json new file mode 100644 index 000000000..59c5171c7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/input.json @@ -0,0 +1,149 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "batches", + "commands": [ + { + "name": "create", + "description": "Creates and executes a batch from an uploaded file of requests", + "options": [ + { + "name": "input-file-id", + "group": "body", + "required": true, + "description": "The ID of an uploaded file that contains requests for the new batch.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour input file must be formatted as a [JSONL file](/docs/api-reference/batch/request-input), and must be uploaded with the purpose `batch`. The file can contain up to 50,000 requests, and can be up to 200 MB in size.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "endpoint", + "group": "body", + "required": true, + "description": "The endpoint to be used for all requests in the batch. Currently `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`, `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, and `/v1/videos` are supported. Note that `/v1/embeddings` batches are also restricted to a maximum of 50,000 embedding inputs across all requests in the batch.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "/v1/responses", + "/v1/chat/completions", + "/v1/embeddings", + "/v1/completions", + "/v1/moderations", + "/v1/images/generations", + "/v1/images/edits", + "/v1/videos" + ] + } + ] + }, + { + "name": "completion-window", + "group": "body", + "required": true, + "description": "The time frame within which the batch should be processed. Currently only `24h` is supported.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "24h" + ] + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "output-expires-after", + "group": "body", + "description": "The expiration policy for the output and/or error file that are generated for a batch.", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/batches", + "operationId": "createBatch", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "input_file_id", + "from": "option:input-file-id", + "jsonPath": "input_file_id", + "encoding": "string", + "required": true + }, + { + "name": "endpoint", + "from": "option:endpoint", + "jsonPath": "endpoint", + "encoding": "string", + "required": true + }, + { + "name": "completion_window", + "from": "option:completion-window", + "jsonPath": "completion_window", + "encoding": "string", + "required": true + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "output_expires_after", + "from": "option:output-expires-after", + "jsonPath": "output_expires_after", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/output.rs new file mode 100644 index 000000000..d28603120 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/output.rs @@ -0,0 +1,96 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("batches") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates and executes a batch from an uploaded file of requests") + .arg( + Arg::new("input-file-id") + .long("input-file-id") + .help("The ID of an uploaded file that contains requests for the new batch.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour input file must be formatted as a [JSONL file](/docs/api-reference/batch/request-input), and must be uploaded with the purpose `batch`. The file can contain up to 50,000 requests, and can be up to 200 MB in size.\n") + .required(true), + ) + .arg( + Arg::new("endpoint") + .long("endpoint") + .help("The endpoint to be used for all requests in the batch. Currently `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`, `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, and `/v1/videos` are supported. Note that `/v1/embeddings` batches are also restricted to a maximum of 50,000 embedding inputs across all requests in the batch.") + .required(true), + ) + .arg( + Arg::new("completion-window") + .long("completion-window") + .help("The time frame within which the batch should be processed. Currently only `24h` is supported.") + .required(true), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("output-expires-after") + .long("output-expires-after") + .help("The expiration policy for the output and/or error file that are generated for a batch."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["batches", "create"] => handle_batches_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_batches_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/batches".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("input-file-id") { + body.insert("input_file_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("endpoint") { + body.insert("endpoint".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("completion-window") { + body.insert("completion_window".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("output-expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("output_expires_after".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/recorded.json new file mode 100644 index 000000000..54fcf888d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__create/recorded.json @@ -0,0 +1,24 @@ +{ + "args": [ + "batches", + "create", + "--input-file-id", + "EXAMPLE", + "--endpoint", + "EXAMPLE", + "--completion-window", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/batches", + "query": [], + "bodyFields": [ + "completion_window", + "endpoint", + "input_file_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/input.json new file mode 100644 index 000000000..1da787bec --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/input.json @@ -0,0 +1,76 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "batches", + "commands": [ + { + "name": "list", + "description": "List your organization's batches.", + "options": [ + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/batches", + "operationId": "listBatches", + "params": [ + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/output.rs new file mode 100644 index 000000000..370ca5551 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/output.rs @@ -0,0 +1,63 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("batches") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List your organization's batches.") + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["batches", "list"] => handle_batches_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_batches_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/batches".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/recorded.json new file mode 100644 index 000000000..15baf7278 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__list/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "batches", + "list" + ], + "request": { + "method": "get", + "path": "/batches", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/input.json new file mode 100644 index 000000000..1da787bec --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/input.json @@ -0,0 +1,76 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "batches", + "commands": [ + { + "name": "list", + "description": "List your organization's batches.", + "options": [ + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/batches", + "operationId": "listBatches", + "params": [ + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/output.rs new file mode 100644 index 000000000..370ca5551 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/output.rs @@ -0,0 +1,63 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("batches") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List your organization's batches.") + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["batches", "list"] => handle_batches_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_batches_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/batches".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/recorded.json new file mode 100644 index 000000000..15baf7278 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/batches__retrieve/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "batches", + "list" + ], + "request": { + "method": "get", + "path": "/batches", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/input.json new file mode 100644 index 000000000..5e962ad3d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/input.json @@ -0,0 +1,223 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "assistants", + "commands": [ + { + "name": "create", + "description": "Create an assistant with a model and instructions.", + "options": [ + { + "name": "model", + "group": "body", + "required": true, + "description": "ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "name", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "description", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "instructions", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "reasoning-effort", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant. Tools can be of types `code_interpreter`, `file_search`, or `function`.\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-resources", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "temperature", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-p", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "response-format", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/assistants", + "operationId": "createAssistant", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "json" + }, + { + "name": "description", + "from": "option:description", + "jsonPath": "description", + "encoding": "json" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "json" + }, + { + "name": "reasoning_effort", + "from": "option:reasoning-effort", + "jsonPath": "reasoning_effort", + "encoding": "json" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_resources", + "from": "option:tool-resources", + "jsonPath": "tool_resources", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "json" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "json" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/output.rs new file mode 100644 index 000000000..e5dc4e708 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/output.rs @@ -0,0 +1,152 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("assistants") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create an assistant with a model and instructions.") + .arg( + Arg::new("model") + .long("model") + .help("ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n") + .required(true), + ) + .arg( + Arg::new("name") + .long("name"), + ) + .arg( + Arg::new("description") + .long("description"), + ) + .arg( + Arg::new("instructions") + .long("instructions"), + ) + .arg( + Arg::new("reasoning-effort") + .long("reasoning-effort"), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant. Tools can be of types `code_interpreter`, `file_search`, or `function`.\n"), + ) + .arg( + Arg::new("tool-resources") + .long("tool-resources"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("temperature") + .long("temperature"), + ) + .arg( + Arg::new("top-p") + .long("top-p"), + ) + .arg( + Arg::new("response-format") + .long("response-format"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["assistants", "create"] => handle_assistants_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_assistants_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/assistants".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("name") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("name".to_string(), value); + } + if let Some(v) = m.get_one::("description") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("description".to_string(), value); + } + if let Some(v) = m.get_one::("instructions") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("instructions".to_string(), value); + } + if let Some(v) = m.get_one::("reasoning-effort") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("reasoning_effort".to_string(), value); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-resources") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_resources".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("temperature") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("temperature".to_string(), value); + } + if let Some(v) = m.get_one::("top-p") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("top_p".to_string(), value); + } + if let Some(v) = m.get_one::("response-format") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("response_format".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/recorded.json new file mode 100644 index 000000000..29bccb80e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__create/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "assistants", + "create", + "--model", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/assistants", + "query": [], + "bodyFields": [ + "model" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/input.json new file mode 100644 index 000000000..24a418545 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "assistants", + "commands": [ + { + "name": "delete", + "description": "Delete an assistant.", + "arguments": [ + { + "name": "assistant-id", + "required": true, + "description": "The ID of the assistant to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/assistants/{assistant_id}", + "operationId": "deleteAssistant", + "params": [ + { + "in": "path", + "name": "assistant_id", + "from": "argument:assistant-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/output.rs new file mode 100644 index 000000000..c9b58cc63 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("assistants") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete an assistant.") + .arg( + Arg::new("assistant-id") + .help("The ID of the assistant to delete.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["assistants", "delete"] => handle_assistants_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_assistants_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let assistant_id = m.get_one::("assistant-id").map(String::as_str).unwrap_or(""); + let path = format!("/assistants/{}", runtime::path_escape(assistant_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/recorded.json new file mode 100644 index 000000000..a85f1c1c7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "assistants", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/assistants/{assistant_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/input.json new file mode 100644 index 000000000..9e58823a4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/input.json @@ -0,0 +1,110 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "assistants", + "commands": [ + { + "name": "list", + "description": "Returns a list of assistants.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/assistants", + "operationId": "listAssistants", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/output.rs new file mode 100644 index 000000000..8103322ce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("assistants") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of assistants.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["assistants", "list"] => handle_assistants_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_assistants_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/assistants".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/recorded.json new file mode 100644 index 000000000..5231d0a5b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__list/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "assistants", + "list" + ], + "request": { + "method": "get", + "path": "/assistants", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/input.json new file mode 100644 index 000000000..9e58823a4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/input.json @@ -0,0 +1,110 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "assistants", + "commands": [ + { + "name": "list", + "description": "Returns a list of assistants.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/assistants", + "operationId": "listAssistants", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/output.rs new file mode 100644 index 000000000..8103322ce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("assistants") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of assistants.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["assistants", "list"] => handle_assistants_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_assistants_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/assistants".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/recorded.json new file mode 100644 index 000000000..5231d0a5b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__retrieve/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "assistants", + "list" + ], + "request": { + "method": "get", + "path": "/assistants", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/input.json new file mode 100644 index 000000000..5e962ad3d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/input.json @@ -0,0 +1,223 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "assistants", + "commands": [ + { + "name": "create", + "description": "Create an assistant with a model and instructions.", + "options": [ + { + "name": "model", + "group": "body", + "required": true, + "description": "ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "name", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "description", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "instructions", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "reasoning-effort", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant. Tools can be of types `code_interpreter`, `file_search`, or `function`.\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-resources", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "temperature", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-p", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "response-format", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/assistants", + "operationId": "createAssistant", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "json" + }, + { + "name": "description", + "from": "option:description", + "jsonPath": "description", + "encoding": "json" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "json" + }, + { + "name": "reasoning_effort", + "from": "option:reasoning-effort", + "jsonPath": "reasoning_effort", + "encoding": "json" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_resources", + "from": "option:tool-resources", + "jsonPath": "tool_resources", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "json" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "json" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/output.rs new file mode 100644 index 000000000..e5dc4e708 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/output.rs @@ -0,0 +1,152 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("assistants") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create an assistant with a model and instructions.") + .arg( + Arg::new("model") + .long("model") + .help("ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n") + .required(true), + ) + .arg( + Arg::new("name") + .long("name"), + ) + .arg( + Arg::new("description") + .long("description"), + ) + .arg( + Arg::new("instructions") + .long("instructions"), + ) + .arg( + Arg::new("reasoning-effort") + .long("reasoning-effort"), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant. Tools can be of types `code_interpreter`, `file_search`, or `function`.\n"), + ) + .arg( + Arg::new("tool-resources") + .long("tool-resources"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("temperature") + .long("temperature"), + ) + .arg( + Arg::new("top-p") + .long("top-p"), + ) + .arg( + Arg::new("response-format") + .long("response-format"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["assistants", "create"] => handle_assistants_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_assistants_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/assistants".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("name") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("name".to_string(), value); + } + if let Some(v) = m.get_one::("description") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("description".to_string(), value); + } + if let Some(v) = m.get_one::("instructions") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("instructions".to_string(), value); + } + if let Some(v) = m.get_one::("reasoning-effort") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("reasoning_effort".to_string(), value); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-resources") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_resources".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("temperature") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("temperature".to_string(), value); + } + if let Some(v) = m.get_one::("top-p") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("top_p".to_string(), value); + } + if let Some(v) = m.get_one::("response-format") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("response_format".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/recorded.json new file mode 100644 index 000000000..29bccb80e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__assistants__update/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "assistants", + "create", + "--model", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/assistants", + "query": [], + "bodyFields": [ + "model" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/input.json new file mode 100644 index 000000000..ca6b87651 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/input.json @@ -0,0 +1,140 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chatkit", + "commands": [ + { + "name": "sessions", + "commands": [ + { + "name": "create", + "description": "Create a ChatKit session.", + "options": [ + { + "name": "workflow", + "group": "body", + "required": true, + "description": "Workflow that powers the session.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "user", + "group": "body", + "required": true, + "description": "A free-form string that identifies your end user; ensures this Session can access other objects that have the same `user` scope.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "Optional override for session expiration timing in seconds from creation. Defaults to 10 minutes.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "rate-limits", + "group": "body", + "description": "Optional override for per-minute request limits. When omitted, defaults to 10.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "chatkit-configuration", + "group": "body", + "description": "Optional overrides for ChatKit runtime configuration features", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/chatkit/sessions", + "operationId": "CreateChatSessionMethod", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "workflow", + "from": "option:workflow", + "jsonPath": "workflow", + "encoding": "json", + "required": true + }, + { + "name": "user", + "from": "option:user", + "jsonPath": "user", + "encoding": "string", + "required": true + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + }, + { + "name": "rate_limits", + "from": "option:rate-limits", + "jsonPath": "rate_limits", + "encoding": "json" + }, + { + "name": "chatkit_configuration", + "from": "option:chatkit-configuration", + "jsonPath": "chatkit_configuration", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/output.rs new file mode 100644 index 000000000..b129917e9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/output.rs @@ -0,0 +1,105 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chatkit") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("sessions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a ChatKit session.") + .arg( + Arg::new("workflow") + .long("workflow") + .help("Workflow that powers the session.") + .required(true), + ) + .arg( + Arg::new("user") + .long("user") + .help("A free-form string that identifies your end user; ensures this Session can access other objects that have the same `user` scope.") + .required(true), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("Optional override for session expiration timing in seconds from creation. Defaults to 10 minutes."), + ) + .arg( + Arg::new("rate-limits") + .long("rate-limits") + .help("Optional override for per-minute request limits. When omitted, defaults to 10."), + ) + .arg( + Arg::new("chatkit-configuration") + .long("chatkit-configuration") + .help("Optional overrides for ChatKit runtime configuration features"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chatkit", "sessions", "create"] => handle_chatkit_sessions_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chatkit_sessions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/chatkit/sessions".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("workflow") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("workflow".to_string(), value); + } + if let Some(v) = m.get_one::("user") { + body.insert("user".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + if let Some(v) = m.get_one::("rate-limits") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("rate_limits".to_string(), value); + } + if let Some(v) = m.get_one::("chatkit-configuration") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chatkit_configuration".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/recorded.json new file mode 100644 index 000000000..3b50f985a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__cancel/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "chatkit", + "sessions", + "create", + "--workflow", + "EXAMPLE", + "--user", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/chatkit/sessions", + "query": [], + "bodyFields": [ + "user", + "workflow" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/input.json new file mode 100644 index 000000000..ca6b87651 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/input.json @@ -0,0 +1,140 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chatkit", + "commands": [ + { + "name": "sessions", + "commands": [ + { + "name": "create", + "description": "Create a ChatKit session.", + "options": [ + { + "name": "workflow", + "group": "body", + "required": true, + "description": "Workflow that powers the session.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "user", + "group": "body", + "required": true, + "description": "A free-form string that identifies your end user; ensures this Session can access other objects that have the same `user` scope.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "Optional override for session expiration timing in seconds from creation. Defaults to 10 minutes.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "rate-limits", + "group": "body", + "description": "Optional override for per-minute request limits. When omitted, defaults to 10.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "chatkit-configuration", + "group": "body", + "description": "Optional overrides for ChatKit runtime configuration features", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/chatkit/sessions", + "operationId": "CreateChatSessionMethod", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "workflow", + "from": "option:workflow", + "jsonPath": "workflow", + "encoding": "json", + "required": true + }, + { + "name": "user", + "from": "option:user", + "jsonPath": "user", + "encoding": "string", + "required": true + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + }, + { + "name": "rate_limits", + "from": "option:rate-limits", + "jsonPath": "rate_limits", + "encoding": "json" + }, + { + "name": "chatkit_configuration", + "from": "option:chatkit-configuration", + "jsonPath": "chatkit_configuration", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/output.rs new file mode 100644 index 000000000..b129917e9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/output.rs @@ -0,0 +1,105 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chatkit") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("sessions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a ChatKit session.") + .arg( + Arg::new("workflow") + .long("workflow") + .help("Workflow that powers the session.") + .required(true), + ) + .arg( + Arg::new("user") + .long("user") + .help("A free-form string that identifies your end user; ensures this Session can access other objects that have the same `user` scope.") + .required(true), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("Optional override for session expiration timing in seconds from creation. Defaults to 10 minutes."), + ) + .arg( + Arg::new("rate-limits") + .long("rate-limits") + .help("Optional override for per-minute request limits. When omitted, defaults to 10."), + ) + .arg( + Arg::new("chatkit-configuration") + .long("chatkit-configuration") + .help("Optional overrides for ChatKit runtime configuration features"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chatkit", "sessions", "create"] => handle_chatkit_sessions_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chatkit_sessions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/chatkit/sessions".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("workflow") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("workflow".to_string(), value); + } + if let Some(v) = m.get_one::("user") { + body.insert("user".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + if let Some(v) = m.get_one::("rate-limits") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("rate_limits".to_string(), value); + } + if let Some(v) = m.get_one::("chatkit-configuration") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chatkit_configuration".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/recorded.json new file mode 100644 index 000000000..3b50f985a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__sessions__create/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "chatkit", + "sessions", + "create", + "--workflow", + "EXAMPLE", + "--user", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/chatkit/sessions", + "query": [], + "bodyFields": [ + "user", + "workflow" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/input.json new file mode 100644 index 000000000..c0f2fcd8b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/input.json @@ -0,0 +1,62 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chatkit", + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "delete", + "description": "Delete a ChatKit thread along with its items and stored attachments.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "Identifier of the ChatKit thread to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/chatkit/threads/{thread_id}", + "operationId": "DeleteThreadMethod", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/output.rs new file mode 100644 index 000000000..3cf418e9a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/output.rs @@ -0,0 +1,56 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chatkit") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a ChatKit thread along with its items and stored attachments.") + .arg( + Arg::new("thread-id") + .help("Identifier of the ChatKit thread to delete.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chatkit", "threads", "delete"] => handle_chatkit_threads_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chatkit_threads_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/chatkit/threads/{}", runtime::path_escape(thread_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/recorded.json new file mode 100644 index 000000000..f43f74946 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__delete/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "chatkit", + "threads", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/chatkit/threads/{thread_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/input.json new file mode 100644 index 000000000..46886547e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/input.json @@ -0,0 +1,65 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chatkit", + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieve a ChatKit thread by its identifier.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "Identifier of the ChatKit thread to retrieve." + } + ], + "x-openapi": { + "method": "get", + "path": "/chatkit/threads/{thread_id}", + "operationId": "GetThreadMethod", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/output.rs new file mode 100644 index 000000000..cd67af378 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/output.rs @@ -0,0 +1,57 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chatkit") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieve a ChatKit thread by its identifier.") + .arg( + Arg::new("thread-id") + .help("Identifier of the ChatKit thread to retrieve.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chatkit", "threads", "retrieve"] => handle_chatkit_threads_retrieve(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chatkit_threads_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/chatkit/threads/{}", runtime::path_escape(thread_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/recorded.json new file mode 100644 index 000000000..01efbb5c1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list-items/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "chatkit", + "threads", + "retrieve", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/chatkit/threads/{thread_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/input.json new file mode 100644 index 000000000..46886547e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/input.json @@ -0,0 +1,65 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chatkit", + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieve a ChatKit thread by its identifier.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "Identifier of the ChatKit thread to retrieve." + } + ], + "x-openapi": { + "method": "get", + "path": "/chatkit/threads/{thread_id}", + "operationId": "GetThreadMethod", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/output.rs new file mode 100644 index 000000000..cd67af378 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/output.rs @@ -0,0 +1,57 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chatkit") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieve a ChatKit thread by its identifier.") + .arg( + Arg::new("thread-id") + .help("Identifier of the ChatKit thread to retrieve.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chatkit", "threads", "retrieve"] => handle_chatkit_threads_retrieve(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chatkit_threads_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/chatkit/threads/{}", runtime::path_escape(thread_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/recorded.json new file mode 100644 index 000000000..01efbb5c1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__list/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "chatkit", + "threads", + "retrieve", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/chatkit/threads/{thread_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/input.json new file mode 100644 index 000000000..46886547e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/input.json @@ -0,0 +1,65 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chatkit", + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieve a ChatKit thread by its identifier.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "Identifier of the ChatKit thread to retrieve." + } + ], + "x-openapi": { + "method": "get", + "path": "/chatkit/threads/{thread_id}", + "operationId": "GetThreadMethod", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/output.rs new file mode 100644 index 000000000..cd67af378 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/output.rs @@ -0,0 +1,57 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chatkit") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieve a ChatKit thread by its identifier.") + .arg( + Arg::new("thread-id") + .help("Identifier of the ChatKit thread to retrieve.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chatkit", "threads", "retrieve"] => handle_chatkit_threads_retrieve(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chatkit_threads_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/chatkit/threads/{}", runtime::path_escape(thread_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/recorded.json new file mode 100644 index 000000000..01efbb5c1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__chatkit__threads__retrieve/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "chatkit", + "threads", + "retrieve", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/chatkit/threads/{thread_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/input.json new file mode 100644 index 000000000..028417e4c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/input.json @@ -0,0 +1,100 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "create", + "description": "Create a thread.", + "options": [ + { + "name": "messages", + "group": "body", + "description": "A list of [messages](/docs/api-reference/messages) to start the thread with.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-resources", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/threads", + "operationId": "createThread", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "messages", + "from": "option:messages", + "jsonPath": "messages", + "encoding": "array" + }, + { + "name": "tool_resources", + "from": "option:tool-resources", + "jsonPath": "tool_resources", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/output.rs new file mode 100644 index 000000000..de78027b1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/output.rs @@ -0,0 +1,78 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a thread.") + .arg( + Arg::new("messages") + .long("messages") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of [messages](/docs/api-reference/messages) to start the thread with."), + ) + .arg( + Arg::new("tool-resources") + .long("tool-resources"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "create"] => handle_threads_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/threads".to_string(); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("messages") { + body.insert("messages".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-resources") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_resources".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/recorded.json new file mode 100644 index 000000000..b47249fce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create-and-run/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "threads", + "create" + ], + "request": { + "method": "post", + "path": "/threads", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/input.json new file mode 100644 index 000000000..028417e4c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/input.json @@ -0,0 +1,100 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "create", + "description": "Create a thread.", + "options": [ + { + "name": "messages", + "group": "body", + "description": "A list of [messages](/docs/api-reference/messages) to start the thread with.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-resources", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/threads", + "operationId": "createThread", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "messages", + "from": "option:messages", + "jsonPath": "messages", + "encoding": "array" + }, + { + "name": "tool_resources", + "from": "option:tool-resources", + "jsonPath": "tool_resources", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/output.rs new file mode 100644 index 000000000..de78027b1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/output.rs @@ -0,0 +1,78 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a thread.") + .arg( + Arg::new("messages") + .long("messages") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of [messages](/docs/api-reference/messages) to start the thread with."), + ) + .arg( + Arg::new("tool-resources") + .long("tool-resources"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "create"] => handle_threads_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/threads".to_string(); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("messages") { + body.insert("messages".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-resources") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_resources".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/recorded.json new file mode 100644 index 000000000..b47249fce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__create/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "threads", + "create" + ], + "request": { + "method": "post", + "path": "/threads", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/input.json new file mode 100644 index 000000000..d8950646b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "delete", + "description": "Delete a thread.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the thread to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/threads/{thread_id}", + "operationId": "deleteThread", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/output.rs new file mode 100644 index 000000000..ad734f9d7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a thread.") + .arg( + Arg::new("thread-id") + .help("The ID of the thread to delete.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "delete"] => handle_threads_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}", runtime::path_escape(thread_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/recorded.json new file mode 100644 index 000000000..e5fcf658f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "threads", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/threads/{thread_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/input.json new file mode 100644 index 000000000..141a5ce03 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/input.json @@ -0,0 +1,140 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "messages", + "commands": [ + { + "name": "create", + "description": "Create a message.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the [thread](/docs/api-reference/threads) to create a message for." + } + ], + "options": [ + { + "name": "role", + "group": "body", + "required": true, + "description": "The role of the entity that is creating the message. Allowed values include:\n- `user`: Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.\n- `assistant`: Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "user", + "assistant" + ] + } + ] + }, + { + "name": "content", + "group": "body", + "required": true, + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "attachments", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/threads/{thread_id}/messages", + "operationId": "createMessage", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "role", + "from": "option:role", + "jsonPath": "role", + "encoding": "string", + "required": true + }, + { + "name": "content", + "from": "option:content", + "jsonPath": "content", + "encoding": "json", + "required": true + }, + { + "name": "attachments", + "from": "option:attachments", + "jsonPath": "attachments", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/output.rs new file mode 100644 index 000000000..28060650c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/output.rs @@ -0,0 +1,98 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("messages") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a message.") + .arg( + Arg::new("thread-id") + .help("The ID of the [thread](/docs/api-reference/threads) to create a message for.") + .required(true), + ) + .arg( + Arg::new("role") + .long("role") + .help("The role of the entity that is creating the message. Allowed values include:\n- `user`: Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.\n- `assistant`: Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.\n") + .required(true), + ) + .arg( + Arg::new("content") + .long("content") + .required(true), + ) + .arg( + Arg::new("attachments") + .long("attachments"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "messages", "create"] => handle_threads_messages_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_messages_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}/messages", runtime::path_escape(thread_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("role") { + body.insert("role".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("content") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("content".to_string(), value); + } + if let Some(v) = m.get_one::("attachments") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("attachments".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/recorded.json new file mode 100644 index 000000000..db6ad978a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__create/recorded.json @@ -0,0 +1,23 @@ +{ + "args": [ + "threads", + "messages", + "create", + "EXAMPLE", + "--role", + "EXAMPLE", + "--content", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/threads/{thread_id}/messages", + "query": [], + "bodyFields": [ + "content", + "role" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/input.json new file mode 100644 index 000000000..cac6aa273 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/input.json @@ -0,0 +1,73 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "messages", + "commands": [ + { + "name": "delete", + "description": "Deletes a message.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the thread to which this message belongs." + }, + { + "name": "message-id", + "required": true, + "description": "The ID of the message to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/threads/{thread_id}/messages/{message_id}", + "operationId": "deleteMessage", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + }, + { + "in": "path", + "name": "message_id", + "from": "argument:message-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/output.rs new file mode 100644 index 000000000..9d949dd41 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/output.rs @@ -0,0 +1,62 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("messages") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Deletes a message.") + .arg( + Arg::new("thread-id") + .help("The ID of the thread to which this message belongs.") + .required(true), + ) + .arg( + Arg::new("message-id") + .help("The ID of the message to delete.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "messages", "delete"] => handle_threads_messages_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_messages_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let message_id = m.get_one::("message-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}/messages/{}", runtime::path_escape(thread_id), runtime::path_escape(message_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/recorded.json new file mode 100644 index 000000000..084f5d4b2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__delete/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "threads", + "messages", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/threads/{thread_id}/messages/{message_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/input.json new file mode 100644 index 000000000..3f2f7670a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/input.json @@ -0,0 +1,143 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "messages", + "commands": [ + { + "name": "list", + "description": "Returns a list of messages for a given thread.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the [thread](/docs/api-reference/threads) the messages belong to." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "run-id", + "description": "Filter messages by the run ID that generated them.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/threads/{thread_id}/messages", + "operationId": "listMessages", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + }, + { + "in": "query", + "name": "run_id", + "from": "option:run-id" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/output.rs new file mode 100644 index 000000000..e2040644c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/output.rs @@ -0,0 +1,98 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("messages") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of messages for a given thread.") + .arg( + Arg::new("thread-id") + .help("The ID of the [thread](/docs/api-reference/threads) the messages belong to.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ) + .arg( + Arg::new("run-id") + .long("run-id") + .help("Filter messages by the run ID that generated them.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "messages", "list"] => handle_threads_messages_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_messages_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}/messages", runtime::path_escape(thread_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + if let Some(v) = m.get_one::("run-id") { + query.push(("run_id", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/recorded.json new file mode 100644 index 000000000..0360374ce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__list/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "threads", + "messages", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/threads/{thread_id}/messages", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/input.json new file mode 100644 index 000000000..3f2f7670a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/input.json @@ -0,0 +1,143 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "messages", + "commands": [ + { + "name": "list", + "description": "Returns a list of messages for a given thread.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the [thread](/docs/api-reference/threads) the messages belong to." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "run-id", + "description": "Filter messages by the run ID that generated them.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/threads/{thread_id}/messages", + "operationId": "listMessages", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + }, + { + "in": "query", + "name": "run_id", + "from": "option:run-id" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/output.rs new file mode 100644 index 000000000..e2040644c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/output.rs @@ -0,0 +1,98 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("messages") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of messages for a given thread.") + .arg( + Arg::new("thread-id") + .help("The ID of the [thread](/docs/api-reference/threads) the messages belong to.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ) + .arg( + Arg::new("run-id") + .long("run-id") + .help("Filter messages by the run ID that generated them.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "messages", "list"] => handle_threads_messages_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_messages_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}/messages", runtime::path_escape(thread_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + if let Some(v) = m.get_one::("run-id") { + query.push(("run_id", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/recorded.json new file mode 100644 index 000000000..0360374ce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__retrieve/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "threads", + "messages", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/threads/{thread_id}/messages", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/input.json new file mode 100644 index 000000000..141a5ce03 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/input.json @@ -0,0 +1,140 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "messages", + "commands": [ + { + "name": "create", + "description": "Create a message.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the [thread](/docs/api-reference/threads) to create a message for." + } + ], + "options": [ + { + "name": "role", + "group": "body", + "required": true, + "description": "The role of the entity that is creating the message. Allowed values include:\n- `user`: Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.\n- `assistant`: Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "user", + "assistant" + ] + } + ] + }, + { + "name": "content", + "group": "body", + "required": true, + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "attachments", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/threads/{thread_id}/messages", + "operationId": "createMessage", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "role", + "from": "option:role", + "jsonPath": "role", + "encoding": "string", + "required": true + }, + { + "name": "content", + "from": "option:content", + "jsonPath": "content", + "encoding": "json", + "required": true + }, + { + "name": "attachments", + "from": "option:attachments", + "jsonPath": "attachments", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/output.rs new file mode 100644 index 000000000..28060650c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/output.rs @@ -0,0 +1,98 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("messages") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a message.") + .arg( + Arg::new("thread-id") + .help("The ID of the [thread](/docs/api-reference/threads) to create a message for.") + .required(true), + ) + .arg( + Arg::new("role") + .long("role") + .help("The role of the entity that is creating the message. Allowed values include:\n- `user`: Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.\n- `assistant`: Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.\n") + .required(true), + ) + .arg( + Arg::new("content") + .long("content") + .required(true), + ) + .arg( + Arg::new("attachments") + .long("attachments"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "messages", "create"] => handle_threads_messages_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_messages_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}/messages", runtime::path_escape(thread_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("role") { + body.insert("role".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("content") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("content".to_string(), value); + } + if let Some(v) = m.get_one::("attachments") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("attachments".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/recorded.json new file mode 100644 index 000000000..db6ad978a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__messages__update/recorded.json @@ -0,0 +1,23 @@ +{ + "args": [ + "threads", + "messages", + "create", + "EXAMPLE", + "--role", + "EXAMPLE", + "--content", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/threads/{thread_id}/messages", + "query": [], + "bodyFields": [ + "content", + "role" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/input.json new file mode 100644 index 000000000..a189634c4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/input.json @@ -0,0 +1,60 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieves a thread.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the thread to retrieve." + } + ], + "x-openapi": { + "method": "get", + "path": "/threads/{thread_id}", + "operationId": "getThread", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/output.rs new file mode 100644 index 000000000..80d5d2c90 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/output.rs @@ -0,0 +1,52 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieves a thread.") + .arg( + Arg::new("thread-id") + .help("The ID of the thread to retrieve.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "retrieve"] => handle_threads_retrieve(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}", runtime::path_escape(thread_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/recorded.json new file mode 100644 index 000000000..51fd75d7f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "threads", + "retrieve", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/threads/{thread_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/input.json new file mode 100644 index 000000000..eda7462eb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/input.json @@ -0,0 +1,304 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "runs", + "commands": [ + { + "name": "create", + "description": "Create a thread and run it in one request.", + "options": [ + { + "name": "assistant-id", + "group": "body", + "required": true, + "description": "The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "thread", + "group": "body", + "description": "Options to create a new thread. If no thread is provided when running a\nrequest, an empty thread will be created.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "model", + "group": "body", + "description": "The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "instructions", + "group": "body", + "description": "Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis.", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-resources", + "group": "body", + "description": "A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "temperature", + "group": "body", + "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "top-p", + "group": "body", + "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "stream", + "group": "body", + "description": "If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n" + }, + { + "name": "max-prompt-tokens", + "group": "body", + "description": "The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "max-completion-tokens", + "group": "body", + "description": "The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "truncation-strategy", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "description": "Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use." + }, + { + "name": "response-format", + "group": "body", + "description": "Specifies the format that the model must output. Compatible with [GPT-4o](/docs/models#gpt-4o), [GPT-4 Turbo](/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/threads/runs", + "operationId": "createThreadAndRun", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "assistant_id", + "from": "option:assistant-id", + "jsonPath": "assistant_id", + "encoding": "string", + "required": true + }, + { + "name": "thread", + "from": "option:thread", + "jsonPath": "thread", + "encoding": "json" + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "string" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_resources", + "from": "option:tool-resources", + "jsonPath": "tool_resources", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "number" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "number" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "boolean" + }, + { + "name": "max_prompt_tokens", + "from": "option:max-prompt-tokens", + "jsonPath": "max_prompt_tokens", + "encoding": "integer" + }, + { + "name": "max_completion_tokens", + "from": "option:max-completion-tokens", + "jsonPath": "max_completion_tokens", + "encoding": "integer" + }, + { + "name": "truncation_strategy", + "from": "option:truncation-strategy", + "jsonPath": "truncation_strategy", + "encoding": "json" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "boolean" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/output.rs new file mode 100644 index 000000000..19fbcd91f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/output.rs @@ -0,0 +1,209 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("runs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a thread and run it in one request.") + .arg( + Arg::new("assistant-id") + .long("assistant-id") + .help("The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run.") + .required(true), + ) + .arg( + Arg::new("thread") + .long("thread") + .help("Options to create a new thread. If no thread is provided when running a\nrequest, an empty thread will be created.\n"), + ) + .arg( + Arg::new("model") + .long("model") + .help("The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used."), + ) + .arg( + Arg::new("instructions") + .long("instructions") + .help("Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis."), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis."), + ) + .arg( + Arg::new("tool-resources") + .long("tool-resources") + .help("A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("temperature") + .long("temperature") + .value_parser(clap::value_parser!(f64)) + .help("What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n"), + ) + .arg( + Arg::new("top-p") + .long("top-p") + .value_parser(clap::value_parser!(f64)) + .help("An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n"), + ) + .arg( + Arg::new("stream") + .long("stream") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n"), + ) + .arg( + Arg::new("max-prompt-tokens") + .long("max-prompt-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n"), + ) + .arg( + Arg::new("max-completion-tokens") + .long("max-completion-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n"), + ) + .arg( + Arg::new("truncation-strategy") + .long("truncation-strategy"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use."), + ) + .arg( + Arg::new("response-format") + .long("response-format") + .help("Specifies the format that the model must output. Compatible with [GPT-4o](/docs/models#gpt-4o), [GPT-4 Turbo](/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "runs", "create"] => handle_threads_runs_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_runs_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/threads/runs".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("assistant-id") { + body.insert("assistant_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("thread") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("thread".to_string(), value); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("instructions") { + body.insert("instructions".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-resources") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_resources".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("temperature") { + body.insert("temperature".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("top-p") { + body.insert("top_p".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("stream") { + body.insert("stream".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("max-prompt-tokens") { + body.insert("max_prompt_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("max-completion-tokens") { + body.insert("max_completion_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("truncation-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("truncation_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + body.insert("parallel_tool_calls".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("response-format") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("response_format".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/recorded.json new file mode 100644 index 000000000..93a066514 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__cancel/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "threads", + "runs", + "create", + "--assistant-id", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/threads/runs", + "query": [], + "bodyFields": [ + "assistant_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/input.json new file mode 100644 index 000000000..eda7462eb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/input.json @@ -0,0 +1,304 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "runs", + "commands": [ + { + "name": "create", + "description": "Create a thread and run it in one request.", + "options": [ + { + "name": "assistant-id", + "group": "body", + "required": true, + "description": "The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "thread", + "group": "body", + "description": "Options to create a new thread. If no thread is provided when running a\nrequest, an empty thread will be created.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "model", + "group": "body", + "description": "The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "instructions", + "group": "body", + "description": "Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis.", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-resources", + "group": "body", + "description": "A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "temperature", + "group": "body", + "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "top-p", + "group": "body", + "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "stream", + "group": "body", + "description": "If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n" + }, + { + "name": "max-prompt-tokens", + "group": "body", + "description": "The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "max-completion-tokens", + "group": "body", + "description": "The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "truncation-strategy", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "description": "Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use." + }, + { + "name": "response-format", + "group": "body", + "description": "Specifies the format that the model must output. Compatible with [GPT-4o](/docs/models#gpt-4o), [GPT-4 Turbo](/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/threads/runs", + "operationId": "createThreadAndRun", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "assistant_id", + "from": "option:assistant-id", + "jsonPath": "assistant_id", + "encoding": "string", + "required": true + }, + { + "name": "thread", + "from": "option:thread", + "jsonPath": "thread", + "encoding": "json" + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "string" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_resources", + "from": "option:tool-resources", + "jsonPath": "tool_resources", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "number" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "number" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "boolean" + }, + { + "name": "max_prompt_tokens", + "from": "option:max-prompt-tokens", + "jsonPath": "max_prompt_tokens", + "encoding": "integer" + }, + { + "name": "max_completion_tokens", + "from": "option:max-completion-tokens", + "jsonPath": "max_completion_tokens", + "encoding": "integer" + }, + { + "name": "truncation_strategy", + "from": "option:truncation-strategy", + "jsonPath": "truncation_strategy", + "encoding": "json" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "boolean" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/output.rs new file mode 100644 index 000000000..19fbcd91f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/output.rs @@ -0,0 +1,209 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("runs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a thread and run it in one request.") + .arg( + Arg::new("assistant-id") + .long("assistant-id") + .help("The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run.") + .required(true), + ) + .arg( + Arg::new("thread") + .long("thread") + .help("Options to create a new thread. If no thread is provided when running a\nrequest, an empty thread will be created.\n"), + ) + .arg( + Arg::new("model") + .long("model") + .help("The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used."), + ) + .arg( + Arg::new("instructions") + .long("instructions") + .help("Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis."), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis."), + ) + .arg( + Arg::new("tool-resources") + .long("tool-resources") + .help("A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("temperature") + .long("temperature") + .value_parser(clap::value_parser!(f64)) + .help("What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n"), + ) + .arg( + Arg::new("top-p") + .long("top-p") + .value_parser(clap::value_parser!(f64)) + .help("An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n"), + ) + .arg( + Arg::new("stream") + .long("stream") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n"), + ) + .arg( + Arg::new("max-prompt-tokens") + .long("max-prompt-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n"), + ) + .arg( + Arg::new("max-completion-tokens") + .long("max-completion-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n"), + ) + .arg( + Arg::new("truncation-strategy") + .long("truncation-strategy"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use."), + ) + .arg( + Arg::new("response-format") + .long("response-format") + .help("Specifies the format that the model must output. Compatible with [GPT-4o](/docs/models#gpt-4o), [GPT-4 Turbo](/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "runs", "create"] => handle_threads_runs_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_runs_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/threads/runs".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("assistant-id") { + body.insert("assistant_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("thread") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("thread".to_string(), value); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("instructions") { + body.insert("instructions".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-resources") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_resources".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("temperature") { + body.insert("temperature".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("top-p") { + body.insert("top_p".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("stream") { + body.insert("stream".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("max-prompt-tokens") { + body.insert("max_prompt_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("max-completion-tokens") { + body.insert("max_completion_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("truncation-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("truncation_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + body.insert("parallel_tool_calls".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("response-format") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("response_format".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/recorded.json new file mode 100644 index 000000000..93a066514 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__create/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "threads", + "runs", + "create", + "--assistant-id", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/threads/runs", + "query": [], + "bodyFields": [ + "assistant_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/input.json new file mode 100644 index 000000000..b13bd6b2a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/input.json @@ -0,0 +1,128 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "runs", + "commands": [ + { + "name": "list", + "description": "Returns a list of runs belonging to a thread.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the thread the run belongs to." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/threads/{thread_id}/runs", + "operationId": "listRuns", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/output.rs new file mode 100644 index 000000000..fde625d42 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/output.rs @@ -0,0 +1,90 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("runs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of runs belonging to a thread.") + .arg( + Arg::new("thread-id") + .help("The ID of the thread the run belongs to.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "runs", "list"] => handle_threads_runs_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_runs_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}/runs", runtime::path_escape(thread_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/recorded.json new file mode 100644 index 000000000..9981be213 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__list/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "threads", + "runs", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/threads/{thread_id}/runs", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/input.json new file mode 100644 index 000000000..b13bd6b2a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/input.json @@ -0,0 +1,128 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "runs", + "commands": [ + { + "name": "list", + "description": "Returns a list of runs belonging to a thread.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the thread the run belongs to." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/threads/{thread_id}/runs", + "operationId": "listRuns", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/output.rs new file mode 100644 index 000000000..fde625d42 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/output.rs @@ -0,0 +1,90 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("runs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of runs belonging to a thread.") + .arg( + Arg::new("thread-id") + .help("The ID of the thread the run belongs to.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "runs", "list"] => handle_threads_runs_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_runs_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}/runs", runtime::path_escape(thread_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/recorded.json new file mode 100644 index 000000000..9981be213 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__retrieve/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "threads", + "runs", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/threads/{thread_id}/runs", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/input.json new file mode 100644 index 000000000..660d9240f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/input.json @@ -0,0 +1,165 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "runs", + "commands": [ + { + "name": "steps", + "commands": [ + { + "name": "list", + "description": "Returns a list of run steps belonging to a run.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the thread the run and run steps belong to." + }, + { + "name": "run-id", + "required": true, + "description": "The ID of the run the run steps belong to." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "include[]", + "description": "A list of additional fields to include in the response. Currently the only supported value is `step_details.tool_calls[*].file_search.results[*].content` to fetch the file search result content.\n\nSee the [file search tool documentation](/docs/assistants/tools/file-search#customizing-file-search-settings) for more information.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "step_details.tool_calls[*].file_search.results[*].content" + ], + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/threads/{thread_id}/runs/{run_id}/steps", + "operationId": "listRunSteps", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + }, + { + "in": "path", + "name": "run_id", + "from": "argument:run-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + }, + { + "in": "query", + "name": "include[]", + "from": "option:include[]" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/output.rs new file mode 100644 index 000000000..1fadb8358 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/output.rs @@ -0,0 +1,111 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("runs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("steps") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of run steps belonging to a run.") + .arg( + Arg::new("thread-id") + .help("The ID of the thread the run and run steps belong to.") + .required(true), + ) + .arg( + Arg::new("run-id") + .help("The ID of the run the run steps belong to.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ) + .arg( + Arg::new("include[]") + .long("include[]") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of additional fields to include in the response. Currently the only supported value is `step_details.tool_calls[*].file_search.results[*].content` to fetch the file search result content.\n\nSee the [file search tool documentation](/docs/assistants/tools/file-search#customizing-file-search-settings) for more information.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "runs", "steps", "list"] => handle_threads_runs_steps_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_runs_steps_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let run_id = m.get_one::("run-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}/runs/{}/steps", runtime::path_escape(thread_id), runtime::path_escape(run_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + if let Some(vs) = m.get_many::("include[]") { + query.push(("include[]", vs.cloned().collect::>().join(","))); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/recorded.json new file mode 100644 index 000000000..0f33d798e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__list/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "threads", + "runs", + "steps", + "list", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/threads/{thread_id}/runs/{run_id}/steps", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/input.json new file mode 100644 index 000000000..660d9240f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/input.json @@ -0,0 +1,165 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "runs", + "commands": [ + { + "name": "steps", + "commands": [ + { + "name": "list", + "description": "Returns a list of run steps belonging to a run.", + "arguments": [ + { + "name": "thread-id", + "required": true, + "description": "The ID of the thread the run and run steps belong to." + }, + { + "name": "run-id", + "required": true, + "description": "The ID of the run the run steps belong to." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "include[]", + "description": "A list of additional fields to include in the response. Currently the only supported value is `step_details.tool_calls[*].file_search.results[*].content` to fetch the file search result content.\n\nSee the [file search tool documentation](/docs/assistants/tools/file-search#customizing-file-search-settings) for more information.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "step_details.tool_calls[*].file_search.results[*].content" + ], + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/threads/{thread_id}/runs/{run_id}/steps", + "operationId": "listRunSteps", + "params": [ + { + "in": "path", + "name": "thread_id", + "from": "argument:thread-id", + "required": true + }, + { + "in": "path", + "name": "run_id", + "from": "argument:run-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + }, + { + "in": "query", + "name": "include[]", + "from": "option:include[]" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/output.rs new file mode 100644 index 000000000..1fadb8358 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/output.rs @@ -0,0 +1,111 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("runs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("steps") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of run steps belonging to a run.") + .arg( + Arg::new("thread-id") + .help("The ID of the thread the run and run steps belong to.") + .required(true), + ) + .arg( + Arg::new("run-id") + .help("The ID of the run the run steps belong to.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ) + .arg( + Arg::new("include[]") + .long("include[]") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of additional fields to include in the response. Currently the only supported value is `step_details.tool_calls[*].file_search.results[*].content` to fetch the file search result content.\n\nSee the [file search tool documentation](/docs/assistants/tools/file-search#customizing-file-search-settings) for more information.\n"), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "runs", "steps", "list"] => handle_threads_runs_steps_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_runs_steps_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let thread_id = m.get_one::("thread-id").map(String::as_str).unwrap_or(""); + let run_id = m.get_one::("run-id").map(String::as_str).unwrap_or(""); + let path = format!("/threads/{}/runs/{}/steps", runtime::path_escape(thread_id), runtime::path_escape(run_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + if let Some(vs) = m.get_many::("include[]") { + query.push(("include[]", vs.cloned().collect::>().join(","))); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/recorded.json new file mode 100644 index 000000000..0f33d798e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__steps__retrieve/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "threads", + "runs", + "steps", + "list", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/threads/{thread_id}/runs/{run_id}/steps", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/input.json new file mode 100644 index 000000000..eda7462eb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/input.json @@ -0,0 +1,304 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "runs", + "commands": [ + { + "name": "create", + "description": "Create a thread and run it in one request.", + "options": [ + { + "name": "assistant-id", + "group": "body", + "required": true, + "description": "The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "thread", + "group": "body", + "description": "Options to create a new thread. If no thread is provided when running a\nrequest, an empty thread will be created.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "model", + "group": "body", + "description": "The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "instructions", + "group": "body", + "description": "Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis.", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-resources", + "group": "body", + "description": "A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "temperature", + "group": "body", + "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "top-p", + "group": "body", + "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "stream", + "group": "body", + "description": "If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n" + }, + { + "name": "max-prompt-tokens", + "group": "body", + "description": "The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "max-completion-tokens", + "group": "body", + "description": "The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "truncation-strategy", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "description": "Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use." + }, + { + "name": "response-format", + "group": "body", + "description": "Specifies the format that the model must output. Compatible with [GPT-4o](/docs/models#gpt-4o), [GPT-4 Turbo](/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/threads/runs", + "operationId": "createThreadAndRun", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "assistant_id", + "from": "option:assistant-id", + "jsonPath": "assistant_id", + "encoding": "string", + "required": true + }, + { + "name": "thread", + "from": "option:thread", + "jsonPath": "thread", + "encoding": "json" + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "string" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_resources", + "from": "option:tool-resources", + "jsonPath": "tool_resources", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "number" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "number" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "boolean" + }, + { + "name": "max_prompt_tokens", + "from": "option:max-prompt-tokens", + "jsonPath": "max_prompt_tokens", + "encoding": "integer" + }, + { + "name": "max_completion_tokens", + "from": "option:max-completion-tokens", + "jsonPath": "max_completion_tokens", + "encoding": "integer" + }, + { + "name": "truncation_strategy", + "from": "option:truncation-strategy", + "jsonPath": "truncation_strategy", + "encoding": "json" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "boolean" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/output.rs new file mode 100644 index 000000000..19fbcd91f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/output.rs @@ -0,0 +1,209 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("runs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a thread and run it in one request.") + .arg( + Arg::new("assistant-id") + .long("assistant-id") + .help("The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run.") + .required(true), + ) + .arg( + Arg::new("thread") + .long("thread") + .help("Options to create a new thread. If no thread is provided when running a\nrequest, an empty thread will be created.\n"), + ) + .arg( + Arg::new("model") + .long("model") + .help("The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used."), + ) + .arg( + Arg::new("instructions") + .long("instructions") + .help("Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis."), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis."), + ) + .arg( + Arg::new("tool-resources") + .long("tool-resources") + .help("A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("temperature") + .long("temperature") + .value_parser(clap::value_parser!(f64)) + .help("What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n"), + ) + .arg( + Arg::new("top-p") + .long("top-p") + .value_parser(clap::value_parser!(f64)) + .help("An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n"), + ) + .arg( + Arg::new("stream") + .long("stream") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n"), + ) + .arg( + Arg::new("max-prompt-tokens") + .long("max-prompt-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n"), + ) + .arg( + Arg::new("max-completion-tokens") + .long("max-completion-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n"), + ) + .arg( + Arg::new("truncation-strategy") + .long("truncation-strategy"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use."), + ) + .arg( + Arg::new("response-format") + .long("response-format") + .help("Specifies the format that the model must output. Compatible with [GPT-4o](/docs/models#gpt-4o), [GPT-4 Turbo](/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "runs", "create"] => handle_threads_runs_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_runs_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/threads/runs".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("assistant-id") { + body.insert("assistant_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("thread") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("thread".to_string(), value); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("instructions") { + body.insert("instructions".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-resources") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_resources".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("temperature") { + body.insert("temperature".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("top-p") { + body.insert("top_p".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("stream") { + body.insert("stream".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("max-prompt-tokens") { + body.insert("max_prompt_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("max-completion-tokens") { + body.insert("max_completion_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("truncation-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("truncation_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + body.insert("parallel_tool_calls".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("response-format") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("response_format".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/recorded.json new file mode 100644 index 000000000..93a066514 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__submit-tool-outputs/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "threads", + "runs", + "create", + "--assistant-id", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/threads/runs", + "query": [], + "bodyFields": [ + "assistant_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/input.json new file mode 100644 index 000000000..eda7462eb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/input.json @@ -0,0 +1,304 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "runs", + "commands": [ + { + "name": "create", + "description": "Create a thread and run it in one request.", + "options": [ + { + "name": "assistant-id", + "group": "body", + "required": true, + "description": "The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "thread", + "group": "body", + "description": "Options to create a new thread. If no thread is provided when running a\nrequest, an empty thread will be created.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "model", + "group": "body", + "description": "The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "instructions", + "group": "body", + "description": "Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis.", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-resources", + "group": "body", + "description": "A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "temperature", + "group": "body", + "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "top-p", + "group": "body", + "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "stream", + "group": "body", + "description": "If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n" + }, + { + "name": "max-prompt-tokens", + "group": "body", + "description": "The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "max-completion-tokens", + "group": "body", + "description": "The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "truncation-strategy", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "description": "Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use." + }, + { + "name": "response-format", + "group": "body", + "description": "Specifies the format that the model must output. Compatible with [GPT-4o](/docs/models#gpt-4o), [GPT-4 Turbo](/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/threads/runs", + "operationId": "createThreadAndRun", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "assistant_id", + "from": "option:assistant-id", + "jsonPath": "assistant_id", + "encoding": "string", + "required": true + }, + { + "name": "thread", + "from": "option:thread", + "jsonPath": "thread", + "encoding": "json" + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "string" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_resources", + "from": "option:tool-resources", + "jsonPath": "tool_resources", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "number" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "number" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "boolean" + }, + { + "name": "max_prompt_tokens", + "from": "option:max-prompt-tokens", + "jsonPath": "max_prompt_tokens", + "encoding": "integer" + }, + { + "name": "max_completion_tokens", + "from": "option:max-completion-tokens", + "jsonPath": "max_completion_tokens", + "encoding": "integer" + }, + { + "name": "truncation_strategy", + "from": "option:truncation-strategy", + "jsonPath": "truncation_strategy", + "encoding": "json" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "boolean" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/output.rs new file mode 100644 index 000000000..19fbcd91f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/output.rs @@ -0,0 +1,209 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("runs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a thread and run it in one request.") + .arg( + Arg::new("assistant-id") + .long("assistant-id") + .help("The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run.") + .required(true), + ) + .arg( + Arg::new("thread") + .long("thread") + .help("Options to create a new thread. If no thread is provided when running a\nrequest, an empty thread will be created.\n"), + ) + .arg( + Arg::new("model") + .long("model") + .help("The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used."), + ) + .arg( + Arg::new("instructions") + .long("instructions") + .help("Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis."), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis."), + ) + .arg( + Arg::new("tool-resources") + .long("tool-resources") + .help("A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("temperature") + .long("temperature") + .value_parser(clap::value_parser!(f64)) + .help("What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n"), + ) + .arg( + Arg::new("top-p") + .long("top-p") + .value_parser(clap::value_parser!(f64)) + .help("An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n"), + ) + .arg( + Arg::new("stream") + .long("stream") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n"), + ) + .arg( + Arg::new("max-prompt-tokens") + .long("max-prompt-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n"), + ) + .arg( + Arg::new("max-completion-tokens") + .long("max-completion-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n"), + ) + .arg( + Arg::new("truncation-strategy") + .long("truncation-strategy"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use."), + ) + .arg( + Arg::new("response-format") + .long("response-format") + .help("Specifies the format that the model must output. Compatible with [GPT-4o](/docs/models#gpt-4o), [GPT-4 Turbo](/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "runs", "create"] => handle_threads_runs_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_runs_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/threads/runs".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("assistant-id") { + body.insert("assistant_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("thread") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("thread".to_string(), value); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("instructions") { + body.insert("instructions".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-resources") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_resources".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("temperature") { + body.insert("temperature".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("top-p") { + body.insert("top_p".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("stream") { + body.insert("stream".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("max-prompt-tokens") { + body.insert("max_prompt_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("max-completion-tokens") { + body.insert("max_completion_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("truncation-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("truncation_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + body.insert("parallel_tool_calls".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("response-format") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("response_format".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/recorded.json new file mode 100644 index 000000000..93a066514 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__runs__update/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "threads", + "runs", + "create", + "--assistant-id", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/threads/runs", + "query": [], + "bodyFields": [ + "assistant_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/input.json new file mode 100644 index 000000000..028417e4c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/input.json @@ -0,0 +1,100 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "threads", + "commands": [ + { + "name": "create", + "description": "Create a thread.", + "options": [ + { + "name": "messages", + "group": "body", + "description": "A list of [messages](/docs/api-reference/messages) to start the thread with.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-resources", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/threads", + "operationId": "createThread", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "messages", + "from": "option:messages", + "jsonPath": "messages", + "encoding": "array" + }, + { + "name": "tool_resources", + "from": "option:tool-resources", + "jsonPath": "tool_resources", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/output.rs new file mode 100644 index 000000000..de78027b1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/output.rs @@ -0,0 +1,78 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("threads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a thread.") + .arg( + Arg::new("messages") + .long("messages") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of [messages](/docs/api-reference/messages) to start the thread with."), + ) + .arg( + Arg::new("tool-resources") + .long("tool-resources"), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["threads", "create"] => handle_threads_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_threads_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/threads".to_string(); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("messages") { + body.insert("messages".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-resources") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_resources".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/recorded.json new file mode 100644 index 000000000..b47249fce --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/beta__threads__update/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "threads", + "create" + ], + "request": { + "method": "post", + "path": "/threads", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/input.json new file mode 100644 index 000000000..6f6349deb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/input.json @@ -0,0 +1,600 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chat", + "commands": [ + { + "name": "completions", + "commands": [ + { + "name": "create", + "description": "**Starting a new project?** We recommend trying [Responses](/docs/api-reference/responses)\nto take advantage of the latest OpenAI platform features. Compare\n[Chat Completions with Responses](/docs/guides/responses-vs-chat-completions?api-mode=responses).\n\n---\n\nCreates a model response for the given chat conversation. Learn more in the\n[text generation](/docs/guides/text-generation), [vision](/docs/guides/vision),\nand [audio](/docs/guides/audio) guides.\n\nParameter support can differ depending on the model used to generate the\nresponse, particularly for newer reasoning models. Parameters that are only\nsupported for reasoning models are noted below. For the current state of\nunsupported parameters in reasoning models,\n[refer to the reasoning guide](/docs/guides/reasoning).\n\nReturns a chat completion object, or a streamed sequence of chat completion\nchunk objects if the request is streamed.\n", + "options": [ + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-logprobs", + "group": "body", + "description": "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n`logprobs` must be set to `true` if this parameter is used.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "temperature", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-p", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "user", + "group": "body", + "description": "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "safety-identifier", + "group": "body", + "description": "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "prompt-cache-key", + "group": "body", + "description": "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "service-tier", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt-cache-retention", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "messages", + "group": "body", + "required": true, + "description": "A list of messages comprising the conversation so far. Depending on the\n[model](/docs/models) you use, different message types (modalities) are\nsupported, like [text](/docs/guides/text-generation),\n[images](/docs/guides/vision), and [audio](/docs/guides/audio).\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "model", + "group": "body", + "required": true, + "description": "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "modalities", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "verbosity", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "reasoning-effort", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "max-completion-tokens", + "group": "body", + "description": "An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "frequency-penalty", + "group": "body", + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on\ntheir existing frequency in the text so far, decreasing the model's\nlikelihood to repeat the same line verbatim.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "presence-penalty", + "group": "body", + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on\nwhether they appear in the text so far, increasing the model's likelihood\nto talk about new topics.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "web-search-options", + "group": "body", + "description": "This tool searches the web for relevant results to use in a response.\nLearn more about the [web search tool](/docs/guides/tools-web-search?api-mode=chat).\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "response-format", + "group": "body", + "description": "An object specifying the format that the model must output.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables\nStructured Outputs which ensures the model will match your supplied JSON\nschema. Learn more in the [Structured Outputs\nguide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables the older JSON mode, which\nensures the message the model generates is valid JSON. Using `json_schema`\nis preferred for models that support it.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "audio", + "group": "body", + "description": "Parameters for audio output. Required when audio output is requested with\n`modalities: [\"audio\"]`. [Learn more](/docs/guides/audio).\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "store", + "group": "body", + "description": "Whether or not to store the output of this chat completion request for\nuse in our [model distillation](/docs/guides/distillation) or\n[evals](/docs/guides/evals) products.\n\nSupports text and image inputs. Note: image inputs over 8MB will be dropped.\n" + }, + { + "name": "stream", + "group": "body", + "description": "If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section below](/docs/api-reference/chat/streaming)\nfor more information, along with the [streaming responses](/docs/guides/streaming-responses)\nguide for more information on how to handle the streaming events.\n" + }, + { + "name": "stop", + "group": "body", + "description": "Not supported with latest reasoning models `o3` and `o4-mini`.\n\nUp to 4 sequences where the API will stop generating further tokens. The\nreturned text will not contain the stop sequence.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "logit-bias", + "group": "body", + "description": "Modify the likelihood of specified tokens appearing in the completion.\n\nAccepts a JSON object that maps tokens (specified by their token ID in the\ntokenizer) to an associated bias value from -100 to 100. Mathematically,\nthe bias is added to the logits generated by the model prior to sampling.\nThe exact effect will vary per model, but values between -1 and 1 should\ndecrease or increase likelihood of selection; values like -100 or 100\nshould result in a ban or exclusive selection of the relevant token.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "logprobs", + "group": "body", + "description": "Whether to return log probabilities of the output tokens or not. If true,\nreturns the log probabilities of each output token returned in the\n`content` of `message`.\n" + }, + { + "name": "max-tokens", + "group": "body", + "description": "The maximum number of [tokens](/tokenizer) that can be generated in the\nchat completion. This value can be used to control\n[costs](https://openai.com/api/pricing/) for text generated via API.\n\nThis value is now deprecated in favor of `max_completion_tokens`, and is\nnot compatible with [o-series models](/docs/guides/reasoning).\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "n", + "group": "body", + "description": "How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs.", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "prediction", + "group": "body", + "description": "Configuration for a [Predicted Output](/docs/guides/predicted-outputs),\nwhich can greatly improve response times when large parts of the model\nresponse are known ahead of time. This is most common when you are\nregenerating a file with only minor changes to most of the content.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "seed", + "group": "body", + "description": "This feature is in Beta.\nIf specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.\nDeterminism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "stream-options", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "A list of tools the model may call. You can provide either\n[custom tools](/docs/guides/function-calling#custom-tools) or\n[function tools](/docs/guides/function-calling).\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "description": "Controls which (if any) tool is called by the model.\n`none` means the model will not call any tool and instead generates a message.\n`auto` means the model can pick between generating a message or calling one or more tools.\n`required` means the model must call one or more tools.\nSpecifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool.\n\n`none` is the default when no tools are present. `auto` is the default if tools are present.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "description": "Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use." + }, + { + "name": "function-call", + "group": "body", + "description": "Deprecated in favor of `tool_choice`.\n\nControls which (if any) function is called by the model.\n\n`none` means the model will not call a function and instead generates a\nmessage.\n\n`auto` means the model can pick between generating a message or calling a\nfunction.\n\nSpecifying a particular function via `{\"name\": \"my_function\"}` forces the\nmodel to call that function.\n\n`none` is the default when no functions are present. `auto` is the default\nif functions are present.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "functions", + "group": "body", + "description": "Deprecated in favor of `tools`.\n\nA list of functions the model may generate JSON inputs for.\n", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/chat/completions", + "operationId": "createChatCompletion", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "top_logprobs", + "from": "option:top-logprobs", + "jsonPath": "top_logprobs", + "encoding": "integer" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "json" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "json" + }, + { + "name": "user", + "from": "option:user", + "jsonPath": "user", + "encoding": "string" + }, + { + "name": "safety_identifier", + "from": "option:safety-identifier", + "jsonPath": "safety_identifier", + "encoding": "string" + }, + { + "name": "prompt_cache_key", + "from": "option:prompt-cache-key", + "jsonPath": "prompt_cache_key", + "encoding": "string" + }, + { + "name": "service_tier", + "from": "option:service-tier", + "jsonPath": "service_tier", + "encoding": "json" + }, + { + "name": "prompt_cache_retention", + "from": "option:prompt-cache-retention", + "jsonPath": "prompt_cache_retention", + "encoding": "json" + }, + { + "name": "messages", + "from": "option:messages", + "jsonPath": "messages", + "encoding": "array", + "required": true + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "modalities", + "from": "option:modalities", + "jsonPath": "modalities", + "encoding": "json" + }, + { + "name": "verbosity", + "from": "option:verbosity", + "jsonPath": "verbosity", + "encoding": "json" + }, + { + "name": "reasoning_effort", + "from": "option:reasoning-effort", + "jsonPath": "reasoning_effort", + "encoding": "json" + }, + { + "name": "max_completion_tokens", + "from": "option:max-completion-tokens", + "jsonPath": "max_completion_tokens", + "encoding": "integer" + }, + { + "name": "frequency_penalty", + "from": "option:frequency-penalty", + "jsonPath": "frequency_penalty", + "encoding": "number" + }, + { + "name": "presence_penalty", + "from": "option:presence-penalty", + "jsonPath": "presence_penalty", + "encoding": "number" + }, + { + "name": "web_search_options", + "from": "option:web-search-options", + "jsonPath": "web_search_options", + "encoding": "json" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "json" + }, + { + "name": "audio", + "from": "option:audio", + "jsonPath": "audio", + "encoding": "json" + }, + { + "name": "store", + "from": "option:store", + "jsonPath": "store", + "encoding": "boolean" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "boolean" + }, + { + "name": "stop", + "from": "option:stop", + "jsonPath": "stop", + "encoding": "json" + }, + { + "name": "logit_bias", + "from": "option:logit-bias", + "jsonPath": "logit_bias", + "encoding": "json" + }, + { + "name": "logprobs", + "from": "option:logprobs", + "jsonPath": "logprobs", + "encoding": "boolean" + }, + { + "name": "max_tokens", + "from": "option:max-tokens", + "jsonPath": "max_tokens", + "encoding": "integer" + }, + { + "name": "n", + "from": "option:n", + "jsonPath": "n", + "encoding": "integer" + }, + { + "name": "prediction", + "from": "option:prediction", + "jsonPath": "prediction", + "encoding": "json" + }, + { + "name": "seed", + "from": "option:seed", + "jsonPath": "seed", + "encoding": "integer" + }, + { + "name": "stream_options", + "from": "option:stream-options", + "jsonPath": "stream_options", + "encoding": "json" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "boolean" + }, + { + "name": "function_call", + "from": "option:function-call", + "jsonPath": "function_call", + "encoding": "json" + }, + { + "name": "functions", + "from": "option:functions", + "jsonPath": "functions", + "encoding": "array" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/output.rs new file mode 100644 index 000000000..5dd5b35d8 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/output.rs @@ -0,0 +1,393 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chat") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("completions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("**Starting a new project?** We recommend trying [Responses](/docs/api-reference/responses)\nto take advantage of the latest OpenAI platform features. Compare\n[Chat Completions with Responses](/docs/guides/responses-vs-chat-completions?api-mode=responses).\n\n---\n\nCreates a model response for the given chat conversation. Learn more in the\n[text generation](/docs/guides/text-generation), [vision](/docs/guides/vision),\nand [audio](/docs/guides/audio) guides.\n\nParameter support can differ depending on the model used to generate the\nresponse, particularly for newer reasoning models. Parameters that are only\nsupported for reasoning models are noted below. For the current state of\nunsupported parameters in reasoning models,\n[refer to the reasoning guide](/docs/guides/reasoning).\n\nReturns a chat completion object, or a streamed sequence of chat completion\nchunk objects if the request is streamed.\n") + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("top-logprobs") + .long("top-logprobs") + .value_parser(clap::value_parser!(i64)) + .help("An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n`logprobs` must be set to `true` if this parameter is used.\n"), + ) + .arg( + Arg::new("temperature") + .long("temperature"), + ) + .arg( + Arg::new("top-p") + .long("top-p"), + ) + .arg( + Arg::new("user") + .long("user") + .help("This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("safety-identifier") + .long("safety-identifier") + .help("A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("prompt-cache-key") + .long("prompt-cache-key") + .help("Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n"), + ) + .arg( + Arg::new("service-tier") + .long("service-tier"), + ) + .arg( + Arg::new("prompt-cache-retention") + .long("prompt-cache-retention"), + ) + .arg( + Arg::new("messages") + .long("messages") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of messages comprising the conversation so far. Depending on the\n[model](/docs/models) you use, different message types (modalities) are\nsupported, like [text](/docs/guides/text-generation),\n[images](/docs/guides/vision), and [audio](/docs/guides/audio).\n") + .required(true), + ) + .arg( + Arg::new("model") + .long("model") + .help("Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n") + .required(true), + ) + .arg( + Arg::new("modalities") + .long("modalities"), + ) + .arg( + Arg::new("verbosity") + .long("verbosity"), + ) + .arg( + Arg::new("reasoning-effort") + .long("reasoning-effort"), + ) + .arg( + Arg::new("max-completion-tokens") + .long("max-completion-tokens") + .value_parser(clap::value_parser!(i64)) + .help("An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n"), + ) + .arg( + Arg::new("frequency-penalty") + .long("frequency-penalty") + .value_parser(clap::value_parser!(f64)) + .help("Number between -2.0 and 2.0. Positive values penalize new tokens based on\ntheir existing frequency in the text so far, decreasing the model's\nlikelihood to repeat the same line verbatim.\n"), + ) + .arg( + Arg::new("presence-penalty") + .long("presence-penalty") + .value_parser(clap::value_parser!(f64)) + .help("Number between -2.0 and 2.0. Positive values penalize new tokens based on\nwhether they appear in the text so far, increasing the model's likelihood\nto talk about new topics.\n"), + ) + .arg( + Arg::new("web-search-options") + .long("web-search-options") + .help("This tool searches the web for relevant results to use in a response.\nLearn more about the [web search tool](/docs/guides/tools-web-search?api-mode=chat).\n"), + ) + .arg( + Arg::new("response-format") + .long("response-format") + .help("An object specifying the format that the model must output.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables\nStructured Outputs which ensures the model will match your supplied JSON\nschema. Learn more in the [Structured Outputs\nguide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables the older JSON mode, which\nensures the message the model generates is valid JSON. Using `json_schema`\nis preferred for models that support it.\n"), + ) + .arg( + Arg::new("audio") + .long("audio") + .help("Parameters for audio output. Required when audio output is requested with\n`modalities: [\"audio\"]`. [Learn more](/docs/guides/audio).\n"), + ) + .arg( + Arg::new("store") + .long("store") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether or not to store the output of this chat completion request for\nuse in our [model distillation](/docs/guides/distillation) or\n[evals](/docs/guides/evals) products.\n\nSupports text and image inputs. Note: image inputs over 8MB will be dropped.\n"), + ) + .arg( + Arg::new("stream") + .long("stream") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section below](/docs/api-reference/chat/streaming)\nfor more information, along with the [streaming responses](/docs/guides/streaming-responses)\nguide for more information on how to handle the streaming events.\n"), + ) + .arg( + Arg::new("stop") + .long("stop") + .help("Not supported with latest reasoning models `o3` and `o4-mini`.\n\nUp to 4 sequences where the API will stop generating further tokens. The\nreturned text will not contain the stop sequence.\n"), + ) + .arg( + Arg::new("logit-bias") + .long("logit-bias") + .help("Modify the likelihood of specified tokens appearing in the completion.\n\nAccepts a JSON object that maps tokens (specified by their token ID in the\ntokenizer) to an associated bias value from -100 to 100. Mathematically,\nthe bias is added to the logits generated by the model prior to sampling.\nThe exact effect will vary per model, but values between -1 and 1 should\ndecrease or increase likelihood of selection; values like -100 or 100\nshould result in a ban or exclusive selection of the relevant token.\n"), + ) + .arg( + Arg::new("logprobs") + .long("logprobs") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to return log probabilities of the output tokens or not. If true,\nreturns the log probabilities of each output token returned in the\n`content` of `message`.\n"), + ) + .arg( + Arg::new("max-tokens") + .long("max-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of [tokens](/tokenizer) that can be generated in the\nchat completion. This value can be used to control\n[costs](https://openai.com/api/pricing/) for text generated via API.\n\nThis value is now deprecated in favor of `max_completion_tokens`, and is\nnot compatible with [o-series models](/docs/guides/reasoning).\n"), + ) + .arg( + Arg::new("n") + .long("n") + .value_parser(clap::value_parser!(i64)) + .help("How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs."), + ) + .arg( + Arg::new("prediction") + .long("prediction") + .help("Configuration for a [Predicted Output](/docs/guides/predicted-outputs),\nwhich can greatly improve response times when large parts of the model\nresponse are known ahead of time. This is most common when you are\nregenerating a file with only minor changes to most of the content.\n"), + ) + .arg( + Arg::new("seed") + .long("seed") + .value_parser(clap::value_parser!(i64)) + .help("This feature is in Beta.\nIf specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.\nDeterminism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.\n"), + ) + .arg( + Arg::new("stream-options") + .long("stream-options"), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of tools the model may call. You can provide either\n[custom tools](/docs/guides/function-calling#custom-tools) or\n[function tools](/docs/guides/function-calling).\n"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice") + .help("Controls which (if any) tool is called by the model.\n`none` means the model will not call any tool and instead generates a message.\n`auto` means the model can pick between generating a message or calling one or more tools.\n`required` means the model must call one or more tools.\nSpecifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool.\n\n`none` is the default when no tools are present. `auto` is the default if tools are present.\n"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use."), + ) + .arg( + Arg::new("function-call") + .long("function-call") + .help("Deprecated in favor of `tool_choice`.\n\nControls which (if any) function is called by the model.\n\n`none` means the model will not call a function and instead generates a\nmessage.\n\n`auto` means the model can pick between generating a message or calling a\nfunction.\n\nSpecifying a particular function via `{\"name\": \"my_function\"}` forces the\nmodel to call that function.\n\n`none` is the default when no functions are present. `auto` is the default\nif functions are present.\n"), + ) + .arg( + Arg::new("functions") + .long("functions") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Deprecated in favor of `tools`.\n\nA list of functions the model may generate JSON inputs for.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chat", "completions", "create"] => handle_chat_completions_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chat_completions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/chat/completions".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("top-logprobs") { + body.insert("top_logprobs".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("temperature") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("temperature".to_string(), value); + } + if let Some(v) = m.get_one::("top-p") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("top_p".to_string(), value); + } + if let Some(v) = m.get_one::("user") { + body.insert("user".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("safety-identifier") { + body.insert("safety_identifier".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("prompt-cache-key") { + body.insert("prompt_cache_key".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("service-tier") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("service_tier".to_string(), value); + } + if let Some(v) = m.get_one::("prompt-cache-retention") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prompt_cache_retention".to_string(), value); + } + if let Some(vs) = m.get_many::("messages") { + body.insert("messages".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("modalities") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("modalities".to_string(), value); + } + if let Some(v) = m.get_one::("verbosity") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("verbosity".to_string(), value); + } + if let Some(v) = m.get_one::("reasoning-effort") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("reasoning_effort".to_string(), value); + } + if let Some(v) = m.get_one::("max-completion-tokens") { + body.insert("max_completion_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("frequency-penalty") { + body.insert("frequency_penalty".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("presence-penalty") { + body.insert("presence_penalty".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("web-search-options") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("web_search_options".to_string(), value); + } + if let Some(v) = m.get_one::("response-format") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("response_format".to_string(), value); + } + if let Some(v) = m.get_one::("audio") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("audio".to_string(), value); + } + if let Some(v) = m.get_one::("store") { + body.insert("store".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("stream") { + body.insert("stream".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("stop") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stop".to_string(), value); + } + if let Some(v) = m.get_one::("logit-bias") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("logit_bias".to_string(), value); + } + if let Some(v) = m.get_one::("logprobs") { + body.insert("logprobs".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("max-tokens") { + body.insert("max_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("n") { + body.insert("n".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("prediction") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prediction".to_string(), value); + } + if let Some(v) = m.get_one::("seed") { + body.insert("seed".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("stream-options") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream_options".to_string(), value); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + body.insert("parallel_tool_calls".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("function-call") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("function_call".to_string(), value); + } + if let Some(vs) = m.get_many::("functions") { + body.insert("functions".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/recorded.json new file mode 100644 index 000000000..a3c1b53f5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__create/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "chat", + "completions", + "create", + "--messages", + "EXAMPLE", + "--model", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/chat/completions", + "query": [], + "bodyFields": [ + "messages", + "model" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/input.json new file mode 100644 index 000000000..a488f5272 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/input.json @@ -0,0 +1,62 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chat", + "commands": [ + { + "name": "completions", + "commands": [ + { + "name": "delete", + "description": "Delete a stored chat completion. Only Chat Completions that have been\ncreated with the `store` parameter set to `true` can be deleted.\n", + "arguments": [ + { + "name": "completion-id", + "required": true, + "description": "The ID of the chat completion to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/chat/completions/{completion_id}", + "operationId": "deleteChatCompletion", + "params": [ + { + "in": "path", + "name": "completion_id", + "from": "argument:completion-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/output.rs new file mode 100644 index 000000000..d00c2ee9a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/output.rs @@ -0,0 +1,56 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chat") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("completions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a stored chat completion. Only Chat Completions that have been\ncreated with the `store` parameter set to `true` can be deleted.\n") + .arg( + Arg::new("completion-id") + .help("The ID of the chat completion to delete.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chat", "completions", "delete"] => handle_chat_completions_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chat_completions_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let completion_id = m.get_one::("completion-id").map(String::as_str).unwrap_or(""); + let path = format!("/chat/completions/{}", runtime::path_escape(completion_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/recorded.json new file mode 100644 index 000000000..2bffef0df --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__delete/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "chat", + "completions", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/chat/completions/{completion_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/input.json new file mode 100644 index 000000000..c178dd3da --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/input.json @@ -0,0 +1,130 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chat", + "commands": [ + { + "name": "completions", + "commands": [ + { + "name": "list", + "description": "List stored Chat Completions. Only Chat Completions that have been stored\nwith the `store` parameter set to `true` will be returned.\n", + "options": [ + { + "name": "model", + "description": "The model used to generate the Chat Completions.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "metadata", + "description": "A list of metadata keys to filter the Chat Completions by. Example:\n\n`metadata[key1]=value1&metadata[key2]=value2`\n", + "group": "query", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "after", + "description": "Identifier for the last chat completion from the previous pagination request.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "Number of Chat Completions to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order for Chat Completions by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/chat/completions", + "operationId": "listChatCompletions", + "params": [ + { + "in": "query", + "name": "model", + "from": "option:model" + }, + { + "in": "query", + "name": "metadata", + "from": "option:metadata" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/output.rs new file mode 100644 index 000000000..6bfc70465 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/output.rs @@ -0,0 +1,92 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chat") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("completions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List stored Chat Completions. Only Chat Completions that have been stored\nwith the `store` parameter set to `true` will be returned.\n") + .arg( + Arg::new("model") + .long("model") + .help("The model used to generate the Chat Completions."), + ) + .arg( + Arg::new("metadata") + .long("metadata") + .help("A list of metadata keys to filter the Chat Completions by. Example:\n\n`metadata[key1]=value1&metadata[key2]=value2`\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last chat completion from the previous pagination request."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of Chat Completions to retrieve."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for Chat Completions by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chat", "completions", "list"] => handle_chat_completions_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chat_completions_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/chat/completions".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("model") { + query.push(("model", v.clone())); + } + if let Some(v) = m.get_one::("metadata") { + query.push(("metadata", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/recorded.json new file mode 100644 index 000000000..c54e1add2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "chat", + "completions", + "list" + ], + "request": { + "method": "get", + "path": "/chat/completions", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/input.json new file mode 100644 index 000000000..a162b5647 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/input.json @@ -0,0 +1,118 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chat", + "commands": [ + { + "name": "completions", + "commands": [ + { + "name": "messages", + "commands": [ + { + "name": "list", + "description": "Get the messages in a stored chat completion. Only Chat Completions that\nhave been created with the `store` parameter set to `true` will be\nreturned.\n", + "arguments": [ + { + "name": "completion-id", + "required": true, + "description": "The ID of the chat completion to retrieve messages from." + } + ], + "options": [ + { + "name": "after", + "description": "Identifier for the last message from the previous pagination request.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "Number of messages to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order for messages by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/chat/completions/{completion_id}/messages", + "operationId": "getChatCompletionMessages", + "params": [ + { + "in": "path", + "name": "completion_id", + "from": "argument:completion-id", + "required": true + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/output.rs new file mode 100644 index 000000000..5fa25aab0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/output.rs @@ -0,0 +1,87 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chat") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("completions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("messages") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Get the messages in a stored chat completion. Only Chat Completions that\nhave been created with the `store` parameter set to `true` will be\nreturned.\n") + .arg( + Arg::new("completion-id") + .help("The ID of the chat completion to retrieve messages from.") + .required(true), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last message from the previous pagination request."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of messages to retrieve."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for messages by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chat", "completions", "messages", "list"] => handle_chat_completions_messages_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chat_completions_messages_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let completion_id = m.get_one::("completion-id").map(String::as_str).unwrap_or(""); + let path = format!("/chat/completions/{}/messages", runtime::path_escape(completion_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/recorded.json new file mode 100644 index 000000000..724b6882b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__messages__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "chat", + "completions", + "messages", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/chat/completions/{completion_id}/messages", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/input.json new file mode 100644 index 000000000..c178dd3da --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/input.json @@ -0,0 +1,130 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chat", + "commands": [ + { + "name": "completions", + "commands": [ + { + "name": "list", + "description": "List stored Chat Completions. Only Chat Completions that have been stored\nwith the `store` parameter set to `true` will be returned.\n", + "options": [ + { + "name": "model", + "description": "The model used to generate the Chat Completions.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "metadata", + "description": "A list of metadata keys to filter the Chat Completions by. Example:\n\n`metadata[key1]=value1&metadata[key2]=value2`\n", + "group": "query", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "after", + "description": "Identifier for the last chat completion from the previous pagination request.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "Number of Chat Completions to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order for Chat Completions by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/chat/completions", + "operationId": "listChatCompletions", + "params": [ + { + "in": "query", + "name": "model", + "from": "option:model" + }, + { + "in": "query", + "name": "metadata", + "from": "option:metadata" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/output.rs new file mode 100644 index 000000000..6bfc70465 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/output.rs @@ -0,0 +1,92 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chat") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("completions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List stored Chat Completions. Only Chat Completions that have been stored\nwith the `store` parameter set to `true` will be returned.\n") + .arg( + Arg::new("model") + .long("model") + .help("The model used to generate the Chat Completions."), + ) + .arg( + Arg::new("metadata") + .long("metadata") + .help("A list of metadata keys to filter the Chat Completions by. Example:\n\n`metadata[key1]=value1&metadata[key2]=value2`\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last chat completion from the previous pagination request."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of Chat Completions to retrieve."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order for Chat Completions by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chat", "completions", "list"] => handle_chat_completions_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chat_completions_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/chat/completions".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("model") { + query.push(("model", v.clone())); + } + if let Some(v) = m.get_one::("metadata") { + query.push(("metadata", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/recorded.json new file mode 100644 index 000000000..c54e1add2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "chat", + "completions", + "list" + ], + "request": { + "method": "get", + "path": "/chat/completions", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/input.json new file mode 100644 index 000000000..6f6349deb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/input.json @@ -0,0 +1,600 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chat", + "commands": [ + { + "name": "completions", + "commands": [ + { + "name": "create", + "description": "**Starting a new project?** We recommend trying [Responses](/docs/api-reference/responses)\nto take advantage of the latest OpenAI platform features. Compare\n[Chat Completions with Responses](/docs/guides/responses-vs-chat-completions?api-mode=responses).\n\n---\n\nCreates a model response for the given chat conversation. Learn more in the\n[text generation](/docs/guides/text-generation), [vision](/docs/guides/vision),\nand [audio](/docs/guides/audio) guides.\n\nParameter support can differ depending on the model used to generate the\nresponse, particularly for newer reasoning models. Parameters that are only\nsupported for reasoning models are noted below. For the current state of\nunsupported parameters in reasoning models,\n[refer to the reasoning guide](/docs/guides/reasoning).\n\nReturns a chat completion object, or a streamed sequence of chat completion\nchunk objects if the request is streamed.\n", + "options": [ + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-logprobs", + "group": "body", + "description": "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n`logprobs` must be set to `true` if this parameter is used.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "temperature", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-p", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "user", + "group": "body", + "description": "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "safety-identifier", + "group": "body", + "description": "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "prompt-cache-key", + "group": "body", + "description": "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "service-tier", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt-cache-retention", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "messages", + "group": "body", + "required": true, + "description": "A list of messages comprising the conversation so far. Depending on the\n[model](/docs/models) you use, different message types (modalities) are\nsupported, like [text](/docs/guides/text-generation),\n[images](/docs/guides/vision), and [audio](/docs/guides/audio).\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "model", + "group": "body", + "required": true, + "description": "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "modalities", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "verbosity", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "reasoning-effort", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "max-completion-tokens", + "group": "body", + "description": "An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "frequency-penalty", + "group": "body", + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on\ntheir existing frequency in the text so far, decreasing the model's\nlikelihood to repeat the same line verbatim.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "presence-penalty", + "group": "body", + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on\nwhether they appear in the text so far, increasing the model's likelihood\nto talk about new topics.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "web-search-options", + "group": "body", + "description": "This tool searches the web for relevant results to use in a response.\nLearn more about the [web search tool](/docs/guides/tools-web-search?api-mode=chat).\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "response-format", + "group": "body", + "description": "An object specifying the format that the model must output.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables\nStructured Outputs which ensures the model will match your supplied JSON\nschema. Learn more in the [Structured Outputs\nguide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables the older JSON mode, which\nensures the message the model generates is valid JSON. Using `json_schema`\nis preferred for models that support it.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "audio", + "group": "body", + "description": "Parameters for audio output. Required when audio output is requested with\n`modalities: [\"audio\"]`. [Learn more](/docs/guides/audio).\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "store", + "group": "body", + "description": "Whether or not to store the output of this chat completion request for\nuse in our [model distillation](/docs/guides/distillation) or\n[evals](/docs/guides/evals) products.\n\nSupports text and image inputs. Note: image inputs over 8MB will be dropped.\n" + }, + { + "name": "stream", + "group": "body", + "description": "If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section below](/docs/api-reference/chat/streaming)\nfor more information, along with the [streaming responses](/docs/guides/streaming-responses)\nguide for more information on how to handle the streaming events.\n" + }, + { + "name": "stop", + "group": "body", + "description": "Not supported with latest reasoning models `o3` and `o4-mini`.\n\nUp to 4 sequences where the API will stop generating further tokens. The\nreturned text will not contain the stop sequence.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "logit-bias", + "group": "body", + "description": "Modify the likelihood of specified tokens appearing in the completion.\n\nAccepts a JSON object that maps tokens (specified by their token ID in the\ntokenizer) to an associated bias value from -100 to 100. Mathematically,\nthe bias is added to the logits generated by the model prior to sampling.\nThe exact effect will vary per model, but values between -1 and 1 should\ndecrease or increase likelihood of selection; values like -100 or 100\nshould result in a ban or exclusive selection of the relevant token.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "logprobs", + "group": "body", + "description": "Whether to return log probabilities of the output tokens or not. If true,\nreturns the log probabilities of each output token returned in the\n`content` of `message`.\n" + }, + { + "name": "max-tokens", + "group": "body", + "description": "The maximum number of [tokens](/tokenizer) that can be generated in the\nchat completion. This value can be used to control\n[costs](https://openai.com/api/pricing/) for text generated via API.\n\nThis value is now deprecated in favor of `max_completion_tokens`, and is\nnot compatible with [o-series models](/docs/guides/reasoning).\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "n", + "group": "body", + "description": "How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs.", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "prediction", + "group": "body", + "description": "Configuration for a [Predicted Output](/docs/guides/predicted-outputs),\nwhich can greatly improve response times when large parts of the model\nresponse are known ahead of time. This is most common when you are\nregenerating a file with only minor changes to most of the content.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "seed", + "group": "body", + "description": "This feature is in Beta.\nIf specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.\nDeterminism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "stream-options", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "A list of tools the model may call. You can provide either\n[custom tools](/docs/guides/function-calling#custom-tools) or\n[function tools](/docs/guides/function-calling).\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "description": "Controls which (if any) tool is called by the model.\n`none` means the model will not call any tool and instead generates a message.\n`auto` means the model can pick between generating a message or calling one or more tools.\n`required` means the model must call one or more tools.\nSpecifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool.\n\n`none` is the default when no tools are present. `auto` is the default if tools are present.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "description": "Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use." + }, + { + "name": "function-call", + "group": "body", + "description": "Deprecated in favor of `tool_choice`.\n\nControls which (if any) function is called by the model.\n\n`none` means the model will not call a function and instead generates a\nmessage.\n\n`auto` means the model can pick between generating a message or calling a\nfunction.\n\nSpecifying a particular function via `{\"name\": \"my_function\"}` forces the\nmodel to call that function.\n\n`none` is the default when no functions are present. `auto` is the default\nif functions are present.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "functions", + "group": "body", + "description": "Deprecated in favor of `tools`.\n\nA list of functions the model may generate JSON inputs for.\n", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/chat/completions", + "operationId": "createChatCompletion", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "top_logprobs", + "from": "option:top-logprobs", + "jsonPath": "top_logprobs", + "encoding": "integer" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "json" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "json" + }, + { + "name": "user", + "from": "option:user", + "jsonPath": "user", + "encoding": "string" + }, + { + "name": "safety_identifier", + "from": "option:safety-identifier", + "jsonPath": "safety_identifier", + "encoding": "string" + }, + { + "name": "prompt_cache_key", + "from": "option:prompt-cache-key", + "jsonPath": "prompt_cache_key", + "encoding": "string" + }, + { + "name": "service_tier", + "from": "option:service-tier", + "jsonPath": "service_tier", + "encoding": "json" + }, + { + "name": "prompt_cache_retention", + "from": "option:prompt-cache-retention", + "jsonPath": "prompt_cache_retention", + "encoding": "json" + }, + { + "name": "messages", + "from": "option:messages", + "jsonPath": "messages", + "encoding": "array", + "required": true + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "modalities", + "from": "option:modalities", + "jsonPath": "modalities", + "encoding": "json" + }, + { + "name": "verbosity", + "from": "option:verbosity", + "jsonPath": "verbosity", + "encoding": "json" + }, + { + "name": "reasoning_effort", + "from": "option:reasoning-effort", + "jsonPath": "reasoning_effort", + "encoding": "json" + }, + { + "name": "max_completion_tokens", + "from": "option:max-completion-tokens", + "jsonPath": "max_completion_tokens", + "encoding": "integer" + }, + { + "name": "frequency_penalty", + "from": "option:frequency-penalty", + "jsonPath": "frequency_penalty", + "encoding": "number" + }, + { + "name": "presence_penalty", + "from": "option:presence-penalty", + "jsonPath": "presence_penalty", + "encoding": "number" + }, + { + "name": "web_search_options", + "from": "option:web-search-options", + "jsonPath": "web_search_options", + "encoding": "json" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "json" + }, + { + "name": "audio", + "from": "option:audio", + "jsonPath": "audio", + "encoding": "json" + }, + { + "name": "store", + "from": "option:store", + "jsonPath": "store", + "encoding": "boolean" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "boolean" + }, + { + "name": "stop", + "from": "option:stop", + "jsonPath": "stop", + "encoding": "json" + }, + { + "name": "logit_bias", + "from": "option:logit-bias", + "jsonPath": "logit_bias", + "encoding": "json" + }, + { + "name": "logprobs", + "from": "option:logprobs", + "jsonPath": "logprobs", + "encoding": "boolean" + }, + { + "name": "max_tokens", + "from": "option:max-tokens", + "jsonPath": "max_tokens", + "encoding": "integer" + }, + { + "name": "n", + "from": "option:n", + "jsonPath": "n", + "encoding": "integer" + }, + { + "name": "prediction", + "from": "option:prediction", + "jsonPath": "prediction", + "encoding": "json" + }, + { + "name": "seed", + "from": "option:seed", + "jsonPath": "seed", + "encoding": "integer" + }, + { + "name": "stream_options", + "from": "option:stream-options", + "jsonPath": "stream_options", + "encoding": "json" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "boolean" + }, + { + "name": "function_call", + "from": "option:function-call", + "jsonPath": "function_call", + "encoding": "json" + }, + { + "name": "functions", + "from": "option:functions", + "jsonPath": "functions", + "encoding": "array" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/output.rs new file mode 100644 index 000000000..5dd5b35d8 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/output.rs @@ -0,0 +1,393 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chat") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("completions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("**Starting a new project?** We recommend trying [Responses](/docs/api-reference/responses)\nto take advantage of the latest OpenAI platform features. Compare\n[Chat Completions with Responses](/docs/guides/responses-vs-chat-completions?api-mode=responses).\n\n---\n\nCreates a model response for the given chat conversation. Learn more in the\n[text generation](/docs/guides/text-generation), [vision](/docs/guides/vision),\nand [audio](/docs/guides/audio) guides.\n\nParameter support can differ depending on the model used to generate the\nresponse, particularly for newer reasoning models. Parameters that are only\nsupported for reasoning models are noted below. For the current state of\nunsupported parameters in reasoning models,\n[refer to the reasoning guide](/docs/guides/reasoning).\n\nReturns a chat completion object, or a streamed sequence of chat completion\nchunk objects if the request is streamed.\n") + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("top-logprobs") + .long("top-logprobs") + .value_parser(clap::value_parser!(i64)) + .help("An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n`logprobs` must be set to `true` if this parameter is used.\n"), + ) + .arg( + Arg::new("temperature") + .long("temperature"), + ) + .arg( + Arg::new("top-p") + .long("top-p"), + ) + .arg( + Arg::new("user") + .long("user") + .help("This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("safety-identifier") + .long("safety-identifier") + .help("A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("prompt-cache-key") + .long("prompt-cache-key") + .help("Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n"), + ) + .arg( + Arg::new("service-tier") + .long("service-tier"), + ) + .arg( + Arg::new("prompt-cache-retention") + .long("prompt-cache-retention"), + ) + .arg( + Arg::new("messages") + .long("messages") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of messages comprising the conversation so far. Depending on the\n[model](/docs/models) you use, different message types (modalities) are\nsupported, like [text](/docs/guides/text-generation),\n[images](/docs/guides/vision), and [audio](/docs/guides/audio).\n") + .required(true), + ) + .arg( + Arg::new("model") + .long("model") + .help("Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n") + .required(true), + ) + .arg( + Arg::new("modalities") + .long("modalities"), + ) + .arg( + Arg::new("verbosity") + .long("verbosity"), + ) + .arg( + Arg::new("reasoning-effort") + .long("reasoning-effort"), + ) + .arg( + Arg::new("max-completion-tokens") + .long("max-completion-tokens") + .value_parser(clap::value_parser!(i64)) + .help("An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n"), + ) + .arg( + Arg::new("frequency-penalty") + .long("frequency-penalty") + .value_parser(clap::value_parser!(f64)) + .help("Number between -2.0 and 2.0. Positive values penalize new tokens based on\ntheir existing frequency in the text so far, decreasing the model's\nlikelihood to repeat the same line verbatim.\n"), + ) + .arg( + Arg::new("presence-penalty") + .long("presence-penalty") + .value_parser(clap::value_parser!(f64)) + .help("Number between -2.0 and 2.0. Positive values penalize new tokens based on\nwhether they appear in the text so far, increasing the model's likelihood\nto talk about new topics.\n"), + ) + .arg( + Arg::new("web-search-options") + .long("web-search-options") + .help("This tool searches the web for relevant results to use in a response.\nLearn more about the [web search tool](/docs/guides/tools-web-search?api-mode=chat).\n"), + ) + .arg( + Arg::new("response-format") + .long("response-format") + .help("An object specifying the format that the model must output.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables\nStructured Outputs which ensures the model will match your supplied JSON\nschema. Learn more in the [Structured Outputs\nguide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables the older JSON mode, which\nensures the message the model generates is valid JSON. Using `json_schema`\nis preferred for models that support it.\n"), + ) + .arg( + Arg::new("audio") + .long("audio") + .help("Parameters for audio output. Required when audio output is requested with\n`modalities: [\"audio\"]`. [Learn more](/docs/guides/audio).\n"), + ) + .arg( + Arg::new("store") + .long("store") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether or not to store the output of this chat completion request for\nuse in our [model distillation](/docs/guides/distillation) or\n[evals](/docs/guides/evals) products.\n\nSupports text and image inputs. Note: image inputs over 8MB will be dropped.\n"), + ) + .arg( + Arg::new("stream") + .long("stream") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section below](/docs/api-reference/chat/streaming)\nfor more information, along with the [streaming responses](/docs/guides/streaming-responses)\nguide for more information on how to handle the streaming events.\n"), + ) + .arg( + Arg::new("stop") + .long("stop") + .help("Not supported with latest reasoning models `o3` and `o4-mini`.\n\nUp to 4 sequences where the API will stop generating further tokens. The\nreturned text will not contain the stop sequence.\n"), + ) + .arg( + Arg::new("logit-bias") + .long("logit-bias") + .help("Modify the likelihood of specified tokens appearing in the completion.\n\nAccepts a JSON object that maps tokens (specified by their token ID in the\ntokenizer) to an associated bias value from -100 to 100. Mathematically,\nthe bias is added to the logits generated by the model prior to sampling.\nThe exact effect will vary per model, but values between -1 and 1 should\ndecrease or increase likelihood of selection; values like -100 or 100\nshould result in a ban or exclusive selection of the relevant token.\n"), + ) + .arg( + Arg::new("logprobs") + .long("logprobs") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to return log probabilities of the output tokens or not. If true,\nreturns the log probabilities of each output token returned in the\n`content` of `message`.\n"), + ) + .arg( + Arg::new("max-tokens") + .long("max-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of [tokens](/tokenizer) that can be generated in the\nchat completion. This value can be used to control\n[costs](https://openai.com/api/pricing/) for text generated via API.\n\nThis value is now deprecated in favor of `max_completion_tokens`, and is\nnot compatible with [o-series models](/docs/guides/reasoning).\n"), + ) + .arg( + Arg::new("n") + .long("n") + .value_parser(clap::value_parser!(i64)) + .help("How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs."), + ) + .arg( + Arg::new("prediction") + .long("prediction") + .help("Configuration for a [Predicted Output](/docs/guides/predicted-outputs),\nwhich can greatly improve response times when large parts of the model\nresponse are known ahead of time. This is most common when you are\nregenerating a file with only minor changes to most of the content.\n"), + ) + .arg( + Arg::new("seed") + .long("seed") + .value_parser(clap::value_parser!(i64)) + .help("This feature is in Beta.\nIf specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.\nDeterminism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.\n"), + ) + .arg( + Arg::new("stream-options") + .long("stream-options"), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of tools the model may call. You can provide either\n[custom tools](/docs/guides/function-calling#custom-tools) or\n[function tools](/docs/guides/function-calling).\n"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice") + .help("Controls which (if any) tool is called by the model.\n`none` means the model will not call any tool and instead generates a message.\n`auto` means the model can pick between generating a message or calling one or more tools.\n`required` means the model must call one or more tools.\nSpecifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool.\n\n`none` is the default when no tools are present. `auto` is the default if tools are present.\n"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use."), + ) + .arg( + Arg::new("function-call") + .long("function-call") + .help("Deprecated in favor of `tool_choice`.\n\nControls which (if any) function is called by the model.\n\n`none` means the model will not call a function and instead generates a\nmessage.\n\n`auto` means the model can pick between generating a message or calling a\nfunction.\n\nSpecifying a particular function via `{\"name\": \"my_function\"}` forces the\nmodel to call that function.\n\n`none` is the default when no functions are present. `auto` is the default\nif functions are present.\n"), + ) + .arg( + Arg::new("functions") + .long("functions") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Deprecated in favor of `tools`.\n\nA list of functions the model may generate JSON inputs for.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chat", "completions", "create"] => handle_chat_completions_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chat_completions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/chat/completions".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("top-logprobs") { + body.insert("top_logprobs".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("temperature") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("temperature".to_string(), value); + } + if let Some(v) = m.get_one::("top-p") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("top_p".to_string(), value); + } + if let Some(v) = m.get_one::("user") { + body.insert("user".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("safety-identifier") { + body.insert("safety_identifier".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("prompt-cache-key") { + body.insert("prompt_cache_key".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("service-tier") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("service_tier".to_string(), value); + } + if let Some(v) = m.get_one::("prompt-cache-retention") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prompt_cache_retention".to_string(), value); + } + if let Some(vs) = m.get_many::("messages") { + body.insert("messages".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("modalities") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("modalities".to_string(), value); + } + if let Some(v) = m.get_one::("verbosity") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("verbosity".to_string(), value); + } + if let Some(v) = m.get_one::("reasoning-effort") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("reasoning_effort".to_string(), value); + } + if let Some(v) = m.get_one::("max-completion-tokens") { + body.insert("max_completion_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("frequency-penalty") { + body.insert("frequency_penalty".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("presence-penalty") { + body.insert("presence_penalty".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("web-search-options") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("web_search_options".to_string(), value); + } + if let Some(v) = m.get_one::("response-format") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("response_format".to_string(), value); + } + if let Some(v) = m.get_one::("audio") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("audio".to_string(), value); + } + if let Some(v) = m.get_one::("store") { + body.insert("store".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("stream") { + body.insert("stream".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("stop") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stop".to_string(), value); + } + if let Some(v) = m.get_one::("logit-bias") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("logit_bias".to_string(), value); + } + if let Some(v) = m.get_one::("logprobs") { + body.insert("logprobs".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("max-tokens") { + body.insert("max_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("n") { + body.insert("n".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("prediction") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prediction".to_string(), value); + } + if let Some(v) = m.get_one::("seed") { + body.insert("seed".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("stream-options") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream_options".to_string(), value); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + body.insert("parallel_tool_calls".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("function-call") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("function_call".to_string(), value); + } + if let Some(vs) = m.get_many::("functions") { + body.insert("functions".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/recorded.json new file mode 100644 index 000000000..a3c1b53f5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/chat__completions__update/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "chat", + "completions", + "create", + "--messages", + "EXAMPLE", + "--model", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/chat/completions", + "query": [], + "bodyFields": [ + "messages", + "model" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/input.json new file mode 100644 index 000000000..02502943c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/input.json @@ -0,0 +1,332 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "completions", + "commands": [ + { + "name": "create", + "description": "Creates a completion for the provided prompt and parameters.\n\nReturns a completion object, or a sequence of completion objects if the request is streamed.\n", + "options": [ + { + "name": "model", + "group": "body", + "required": true, + "description": "ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "required": true, + "description": "The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays.\n\nNote that <|endoftext|> is the document separator that the model sees during training, so if a prompt is not specified the model will generate as if from the beginning of a new document.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "best-of", + "group": "body", + "description": "Generates `best_of` completions server-side and returns the \"best\" (the one with the highest log probability per token). Results cannot be streamed.\n\nWhen used with `n`, `best_of` controls the number of candidate completions and `n` specifies how many to return – `best_of` must be greater than `n`.\n\n**Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "echo", + "group": "body", + "description": "Echo back the prompt in addition to the completion\n" + }, + { + "name": "frequency-penalty", + "group": "body", + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.\n\n[See more information about frequency and presence penalties.](/docs/guides/text-generation)\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "logit-bias", + "group": "body", + "description": "Modify the likelihood of specified tokens appearing in the completion.\n\nAccepts a JSON object that maps tokens (specified by their token ID in the GPT tokenizer) to an associated bias value from -100 to 100. You can use this [tokenizer tool](/tokenizer?view=bpe) to convert text to token IDs. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token.\n\nAs an example, you can pass `{\"50256\": -100}` to prevent the <|endoftext|> token from being generated.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "logprobs", + "group": "body", + "description": "Include the log probabilities on the `logprobs` most likely output tokens, as well the chosen tokens. For example, if `logprobs` is 5, the API will return a list of the 5 most likely tokens. The API will always return the `logprob` of the sampled token, so there may be up to `logprobs+1` elements in the response.\n\nThe maximum value for `logprobs` is 5.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "max-tokens", + "group": "body", + "description": "The maximum number of [tokens](/tokenizer) that can be generated in the completion.\n\nThe token count of your prompt plus `max_tokens` cannot exceed the model's context length. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "n", + "group": "body", + "description": "How many completions to generate for each prompt.\n\n**Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "presence-penalty", + "group": "body", + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.\n\n[See more information about frequency and presence penalties.](/docs/guides/text-generation)\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "seed", + "group": "body", + "description": "If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.\n\nDeterminism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "stop", + "group": "body", + "description": "Not supported with latest reasoning models `o3` and `o4-mini`.\n\nUp to 4 sequences where the API will stop generating further tokens. The\nreturned text will not contain the stop sequence.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "stream", + "group": "body", + "description": "Whether to stream back partial progress. If set, tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) as they become available, with the stream terminated by a `data: [DONE]` message. [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions).\n" + }, + { + "name": "stream-options", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "suffix", + "group": "body", + "description": "The suffix that comes after a completion of inserted text.\n\nThis parameter is only supported for `gpt-3.5-turbo-instruct`.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "temperature", + "group": "body", + "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n\nWe generally recommend altering this or `top_p` but not both.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "top-p", + "group": "body", + "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or `temperature` but not both.\n", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "user", + "group": "body", + "description": "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices#end-user-ids).\n", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/completions", + "operationId": "createCompletion", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "json", + "required": true + }, + { + "name": "best_of", + "from": "option:best-of", + "jsonPath": "best_of", + "encoding": "integer" + }, + { + "name": "echo", + "from": "option:echo", + "jsonPath": "echo", + "encoding": "boolean" + }, + { + "name": "frequency_penalty", + "from": "option:frequency-penalty", + "jsonPath": "frequency_penalty", + "encoding": "number" + }, + { + "name": "logit_bias", + "from": "option:logit-bias", + "jsonPath": "logit_bias", + "encoding": "json" + }, + { + "name": "logprobs", + "from": "option:logprobs", + "jsonPath": "logprobs", + "encoding": "integer" + }, + { + "name": "max_tokens", + "from": "option:max-tokens", + "jsonPath": "max_tokens", + "encoding": "integer" + }, + { + "name": "n", + "from": "option:n", + "jsonPath": "n", + "encoding": "integer" + }, + { + "name": "presence_penalty", + "from": "option:presence-penalty", + "jsonPath": "presence_penalty", + "encoding": "number" + }, + { + "name": "seed", + "from": "option:seed", + "jsonPath": "seed", + "encoding": "integer" + }, + { + "name": "stop", + "from": "option:stop", + "jsonPath": "stop", + "encoding": "json" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "boolean" + }, + { + "name": "stream_options", + "from": "option:stream-options", + "jsonPath": "stream_options", + "encoding": "json" + }, + { + "name": "suffix", + "from": "option:suffix", + "jsonPath": "suffix", + "encoding": "string" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "number" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "number" + }, + { + "name": "user", + "from": "option:user", + "jsonPath": "user", + "encoding": "string" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/output.rs new file mode 100644 index 000000000..356e3f6ac --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/output.rs @@ -0,0 +1,222 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("completions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a completion for the provided prompt and parameters.\n\nReturns a completion object, or a sequence of completion objects if the request is streamed.\n") + .arg( + Arg::new("model") + .long("model") + .help("ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n") + .required(true), + ) + .arg( + Arg::new("prompt") + .long("prompt") + .help("The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays.\n\nNote that <|endoftext|> is the document separator that the model sees during training, so if a prompt is not specified the model will generate as if from the beginning of a new document.\n") + .required(true), + ) + .arg( + Arg::new("best-of") + .long("best-of") + .value_parser(clap::value_parser!(i64)) + .help("Generates `best_of` completions server-side and returns the \"best\" (the one with the highest log probability per token). Results cannot be streamed.\n\nWhen used with `n`, `best_of` controls the number of candidate completions and `n` specifies how many to return – `best_of` must be greater than `n`.\n\n**Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`.\n"), + ) + .arg( + Arg::new("echo") + .long("echo") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Echo back the prompt in addition to the completion\n"), + ) + .arg( + Arg::new("frequency-penalty") + .long("frequency-penalty") + .value_parser(clap::value_parser!(f64)) + .help("Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.\n\n[See more information about frequency and presence penalties.](/docs/guides/text-generation)\n"), + ) + .arg( + Arg::new("logit-bias") + .long("logit-bias") + .help("Modify the likelihood of specified tokens appearing in the completion.\n\nAccepts a JSON object that maps tokens (specified by their token ID in the GPT tokenizer) to an associated bias value from -100 to 100. You can use this [tokenizer tool](/tokenizer?view=bpe) to convert text to token IDs. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token.\n\nAs an example, you can pass `{\"50256\": -100}` to prevent the <|endoftext|> token from being generated.\n"), + ) + .arg( + Arg::new("logprobs") + .long("logprobs") + .value_parser(clap::value_parser!(i64)) + .help("Include the log probabilities on the `logprobs` most likely output tokens, as well the chosen tokens. For example, if `logprobs` is 5, the API will return a list of the 5 most likely tokens. The API will always return the `logprob` of the sampled token, so there may be up to `logprobs+1` elements in the response.\n\nThe maximum value for `logprobs` is 5.\n"), + ) + .arg( + Arg::new("max-tokens") + .long("max-tokens") + .value_parser(clap::value_parser!(i64)) + .help("The maximum number of [tokens](/tokenizer) that can be generated in the completion.\n\nThe token count of your prompt plus `max_tokens` cannot exceed the model's context length. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens.\n"), + ) + .arg( + Arg::new("n") + .long("n") + .value_parser(clap::value_parser!(i64)) + .help("How many completions to generate for each prompt.\n\n**Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`.\n"), + ) + .arg( + Arg::new("presence-penalty") + .long("presence-penalty") + .value_parser(clap::value_parser!(f64)) + .help("Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.\n\n[See more information about frequency and presence penalties.](/docs/guides/text-generation)\n"), + ) + .arg( + Arg::new("seed") + .long("seed") + .value_parser(clap::value_parser!(i64)) + .help("If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.\n\nDeterminism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.\n"), + ) + .arg( + Arg::new("stop") + .long("stop") + .help("Not supported with latest reasoning models `o3` and `o4-mini`.\n\nUp to 4 sequences where the API will stop generating further tokens. The\nreturned text will not contain the stop sequence.\n"), + ) + .arg( + Arg::new("stream") + .long("stream") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to stream back partial progress. If set, tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) as they become available, with the stream terminated by a `data: [DONE]` message. [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions).\n"), + ) + .arg( + Arg::new("stream-options") + .long("stream-options"), + ) + .arg( + Arg::new("suffix") + .long("suffix") + .help("The suffix that comes after a completion of inserted text.\n\nThis parameter is only supported for `gpt-3.5-turbo-instruct`.\n"), + ) + .arg( + Arg::new("temperature") + .long("temperature") + .value_parser(clap::value_parser!(f64)) + .help("What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n\nWe generally recommend altering this or `top_p` but not both.\n"), + ) + .arg( + Arg::new("top-p") + .long("top-p") + .value_parser(clap::value_parser!(f64)) + .help("An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or `temperature` but not both.\n"), + ) + .arg( + Arg::new("user") + .long("user") + .help("A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices#end-user-ids).\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["completions", "create"] => handle_completions_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_completions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/completions".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prompt".to_string(), value); + } + if let Some(v) = m.get_one::("best-of") { + body.insert("best_of".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("echo") { + body.insert("echo".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("frequency-penalty") { + body.insert("frequency_penalty".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("logit-bias") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("logit_bias".to_string(), value); + } + if let Some(v) = m.get_one::("logprobs") { + body.insert("logprobs".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("max-tokens") { + body.insert("max_tokens".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("n") { + body.insert("n".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("presence-penalty") { + body.insert("presence_penalty".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("seed") { + body.insert("seed".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("stop") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stop".to_string(), value); + } + if let Some(v) = m.get_one::("stream") { + body.insert("stream".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("stream-options") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream_options".to_string(), value); + } + if let Some(v) = m.get_one::("suffix") { + body.insert("suffix".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("temperature") { + body.insert("temperature".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("top-p") { + body.insert("top_p".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("user") { + body.insert("user".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/recorded.json new file mode 100644 index 000000000..c0aceeec5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/completions__create/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "completions", + "create", + "--model", + "EXAMPLE", + "--prompt", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/completions", + "query": [], + "bodyFields": [ + "model", + "prompt" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/input.json new file mode 100644 index 000000000..7aa0003b9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/input.json @@ -0,0 +1,161 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "containers", + "commands": [ + { + "name": "create", + "description": "Create Container", + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "description": "Name of the container to create.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "file-ids", + "group": "body", + "description": "IDs of files to copy to the container.", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "Container expiration time in seconds relative to the 'anchor' time.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "skills", + "group": "body", + "description": "An optional list of skills referenced by id or inline data.", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "memory-limit", + "group": "body", + "description": "Optional memory limit for the container. Defaults to \"1g\".", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "1g", + "4g", + "16g", + "64g" + ] + } + ] + }, + { + "name": "network-policy", + "group": "body", + "description": "Network access policy for the container.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/containers", + "operationId": "CreateContainer", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + }, + { + "name": "file_ids", + "from": "option:file-ids", + "jsonPath": "file_ids", + "encoding": "array" + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + }, + { + "name": "skills", + "from": "option:skills", + "jsonPath": "skills", + "encoding": "array" + }, + { + "name": "memory_limit", + "from": "option:memory-limit", + "jsonPath": "memory_limit", + "encoding": "string" + }, + { + "name": "network_policy", + "from": "option:network-policy", + "jsonPath": "network_policy", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/output.rs new file mode 100644 index 000000000..03545e814 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/output.rs @@ -0,0 +1,107 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("containers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create Container") + .arg( + Arg::new("name") + .long("name") + .help("Name of the container to create.") + .required(true), + ) + .arg( + Arg::new("file-ids") + .long("file-ids") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("IDs of files to copy to the container."), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("Container expiration time in seconds relative to the 'anchor' time."), + ) + .arg( + Arg::new("skills") + .long("skills") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("An optional list of skills referenced by id or inline data."), + ) + .arg( + Arg::new("memory-limit") + .long("memory-limit") + .help("Optional memory limit for the container. Defaults to \"1g\"."), + ) + .arg( + Arg::new("network-policy") + .long("network-policy") + .help("Network access policy for the container."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["containers", "create"] => handle_containers_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_containers_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/containers".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("file-ids") { + body.insert("file_ids".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + if let Some(vs) = m.get_many::("skills") { + body.insert("skills".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("memory-limit") { + body.insert("memory_limit".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("network-policy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("network_policy".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/recorded.json new file mode 100644 index 000000000..20e4c2bdf --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__create/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "containers", + "create", + "--name", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/containers", + "query": [], + "bodyFields": [ + "name" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/input.json new file mode 100644 index 000000000..165799600 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "containers", + "commands": [ + { + "name": "delete", + "description": "Delete Container", + "arguments": [ + { + "name": "container-id", + "required": true, + "description": "The ID of the container to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/containers/{container_id}", + "operationId": "DeleteContainer", + "params": [ + { + "in": "path", + "name": "container_id", + "from": "argument:container-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/output.rs new file mode 100644 index 000000000..808d42fad --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("containers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete Container") + .arg( + Arg::new("container-id") + .help("The ID of the container to delete.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["containers", "delete"] => handle_containers_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_containers_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let container_id = m.get_one::("container-id").map(String::as_str).unwrap_or(""); + let path = format!("/containers/{}", runtime::path_escape(container_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/recorded.json new file mode 100644 index 000000000..76bec9dbf --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "containers", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/containers/{container_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/input.json new file mode 100644 index 000000000..593c43455 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/input.json @@ -0,0 +1,76 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "containers", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "content", + "commands": [ + { + "name": "list", + "description": "Retrieve Container File Content", + "arguments": [ + { + "name": "container-id", + "required": true + }, + { + "name": "file-id", + "required": true + } + ], + "x-openapi": { + "method": "get", + "path": "/containers/{container_id}/files/{file_id}/content", + "operationId": "RetrieveContainerFileContent", + "params": [ + { + "in": "path", + "name": "container_id", + "from": "argument:container-id", + "required": true + }, + { + "in": "path", + "name": "file_id", + "from": "argument:file-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/output.rs new file mode 100644 index 000000000..81f6b90fc --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/output.rs @@ -0,0 +1,65 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("containers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("content") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Retrieve Container File Content") + .arg( + Arg::new("container-id") + .required(true), + ) + .arg( + Arg::new("file-id") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["containers", "files", "content", "list"] => handle_containers_files_content_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_containers_files_content_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let container_id = m.get_one::("container-id").map(String::as_str).unwrap_or(""); + let file_id = m.get_one::("file-id").map(String::as_str).unwrap_or(""); + let path = format!("/containers/{}/files/{}/content", runtime::path_escape(container_id), runtime::path_escape(file_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/recorded.json new file mode 100644 index 000000000..551c6ed3a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__content__retrieve/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "containers", + "files", + "content", + "list", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/containers/{container_id}/files/{file_id}/content", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/input.json new file mode 100644 index 000000000..759d4de99 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/input.json @@ -0,0 +1,102 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "containers", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "create", + "description": "Create a Container File\n\nYou can send either a multipart/form-data request with the raw file content, or a JSON request with a file ID.\n", + "arguments": [ + { + "name": "container-id", + "required": true + } + ], + "options": [ + { + "name": "file-id", + "group": "body", + "description": "Name of the file to create.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "file", + "group": "body", + "description": "The File object (not file name) to be uploaded.\n", + "arguments": [ + { + "name": "path" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/containers/{container_id}/files", + "operationId": "CreateContainerFile", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "container_id", + "from": "argument:container-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "file_id", + "from": "option:file-id", + "jsonPath": "file_id", + "encoding": "string" + }, + { + "name": "file", + "from": "option:file", + "jsonPath": "file", + "encoding": "file" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/output.rs new file mode 100644 index 000000000..234fe9eb1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("containers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a Container File\n\nYou can send either a multipart/form-data request with the raw file content, or a JSON request with a file ID.\n") + .arg( + Arg::new("container-id") + .required(true), + ) + .arg( + Arg::new("file-id") + .long("file-id") + .help("Name of the file to create."), + ) + .arg( + Arg::new("file") + .long("file") + .help("The File object (not file name) to be uploaded.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["containers", "files", "create"] => handle_containers_files_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_containers_files_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let container_id = m.get_one::("container-id").map(String::as_str).unwrap_or(""); + let path = format!("/containers/{}/files", runtime::path_escape(container_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("file-id") { + body.insert("file_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("file") { + body.insert("file".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/recorded.json new file mode 100644 index 000000000..acd1dbeac --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__create/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "containers", + "files", + "create", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/containers/{container_id}/files", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/input.json new file mode 100644 index 000000000..9b8ad03ee --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/input.json @@ -0,0 +1,71 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "containers", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "delete", + "description": "Delete Container File", + "arguments": [ + { + "name": "container-id", + "required": true + }, + { + "name": "file-id", + "required": true + } + ], + "x-openapi": { + "method": "delete", + "path": "/containers/{container_id}/files/{file_id}", + "operationId": "DeleteContainerFile", + "params": [ + { + "in": "path", + "name": "container_id", + "from": "argument:container-id", + "required": true + }, + { + "in": "path", + "name": "file_id", + "from": "argument:file-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/output.rs new file mode 100644 index 000000000..089c93d4f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/output.rs @@ -0,0 +1,60 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("containers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete Container File") + .arg( + Arg::new("container-id") + .required(true), + ) + .arg( + Arg::new("file-id") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["containers", "files", "delete"] => handle_containers_files_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_containers_files_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let container_id = m.get_one::("container-id").map(String::as_str).unwrap_or(""); + let file_id = m.get_one::("file-id").map(String::as_str).unwrap_or(""); + let path = format!("/containers/{}/files/{}", runtime::path_escape(container_id), runtime::path_escape(file_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/recorded.json new file mode 100644 index 000000000..e89244022 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__delete/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "containers", + "files", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/containers/{container_id}/files/{file_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/input.json new file mode 100644 index 000000000..4c70e3591 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/input.json @@ -0,0 +1,112 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "containers", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "list", + "description": "List Container files", + "arguments": [ + { + "name": "container-id", + "required": true + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/containers/{container_id}/files", + "operationId": "ListContainerFiles", + "params": [ + { + "in": "path", + "name": "container_id", + "from": "argument:container-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/output.rs new file mode 100644 index 000000000..60b74769b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/output.rs @@ -0,0 +1,81 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("containers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List Container files") + .arg( + Arg::new("container-id") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["containers", "files", "list"] => handle_containers_files_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_containers_files_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let container_id = m.get_one::("container-id").map(String::as_str).unwrap_or(""); + let path = format!("/containers/{}/files", runtime::path_escape(container_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/recorded.json new file mode 100644 index 000000000..58e7c60ee --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__list/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "containers", + "files", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/containers/{container_id}/files", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/input.json new file mode 100644 index 000000000..4c70e3591 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/input.json @@ -0,0 +1,112 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "containers", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "list", + "description": "List Container files", + "arguments": [ + { + "name": "container-id", + "required": true + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/containers/{container_id}/files", + "operationId": "ListContainerFiles", + "params": [ + { + "in": "path", + "name": "container_id", + "from": "argument:container-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/output.rs new file mode 100644 index 000000000..60b74769b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/output.rs @@ -0,0 +1,81 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("containers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List Container files") + .arg( + Arg::new("container-id") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["containers", "files", "list"] => handle_containers_files_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_containers_files_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let container_id = m.get_one::("container-id").map(String::as_str).unwrap_or(""); + let path = format!("/containers/{}/files", runtime::path_escape(container_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/recorded.json new file mode 100644 index 000000000..58e7c60ee --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__files__retrieve/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "containers", + "files", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/containers/{container_id}/files", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/input.json new file mode 100644 index 000000000..3e85f0d82 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/input.json @@ -0,0 +1,110 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "containers", + "commands": [ + { + "name": "list", + "description": "List Containers", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "name", + "description": "Filter results by container name.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/containers", + "operationId": "ListContainers", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "name", + "from": "option:name" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/output.rs new file mode 100644 index 000000000..e73d9571f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("containers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List Containers") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("name") + .long("name") + .help("Filter results by container name."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["containers", "list"] => handle_containers_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_containers_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/containers".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("name") { + query.push(("name", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/recorded.json new file mode 100644 index 000000000..4ea366970 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__list/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "containers", + "list" + ], + "request": { + "method": "get", + "path": "/containers", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/input.json new file mode 100644 index 000000000..3e85f0d82 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/input.json @@ -0,0 +1,110 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "containers", + "commands": [ + { + "name": "list", + "description": "List Containers", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "name", + "description": "Filter results by container name.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/containers", + "operationId": "ListContainers", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "name", + "from": "option:name" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/output.rs new file mode 100644 index 000000000..e73d9571f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("containers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List Containers") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("name") + .long("name") + .help("Filter results by container name."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["containers", "list"] => handle_containers_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_containers_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/containers".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("name") { + query.push(("name", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/recorded.json new file mode 100644 index 000000000..4ea366970 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/containers__retrieve/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "containers", + "list" + ], + "request": { + "method": "get", + "path": "/containers", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/input.json new file mode 100644 index 000000000..3a36dcae8 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/input.json @@ -0,0 +1,81 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "conversations", + "commands": [ + { + "name": "create", + "description": "Create a conversation.", + "options": [ + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "items", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/conversations", + "operationId": "createConversation", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "items", + "from": "option:items", + "jsonPath": "items", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/output.rs new file mode 100644 index 000000000..14d5b0ddf --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/output.rs @@ -0,0 +1,68 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("conversations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a conversation.") + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("items") + .long("items"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["conversations", "create"] => handle_conversations_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_conversations_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/conversations".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("items") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("items".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/recorded.json new file mode 100644 index 000000000..31b2abc87 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__create/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "conversations", + "create" + ], + "request": { + "method": "post", + "path": "/conversations", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/input.json new file mode 100644 index 000000000..e1fb5da29 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "conversations", + "commands": [ + { + "name": "delete", + "description": "Delete a conversation. Items in the conversation will not be deleted.", + "arguments": [ + { + "name": "conversation-id", + "required": true, + "description": "The ID of the conversation to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/conversations/{conversation_id}", + "operationId": "deleteConversation", + "params": [ + { + "in": "path", + "name": "conversation_id", + "from": "argument:conversation-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/output.rs new file mode 100644 index 000000000..eff508f52 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("conversations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a conversation. Items in the conversation will not be deleted.") + .arg( + Arg::new("conversation-id") + .help("The ID of the conversation to delete.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["conversations", "delete"] => handle_conversations_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_conversations_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let conversation_id = m.get_one::("conversation-id").map(String::as_str).unwrap_or(""); + let path = format!("/conversations/{}", runtime::path_escape(conversation_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/recorded.json new file mode 100644 index 000000000..f8ab040a4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "conversations", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/conversations/{conversation_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/input.json new file mode 100644 index 000000000..f43c3f727 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/input.json @@ -0,0 +1,120 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "conversations", + "commands": [ + { + "name": "items", + "commands": [ + { + "name": "create", + "description": "Create items in a conversation with the given ID.", + "arguments": [ + { + "name": "conversation-id", + "required": true, + "description": "The ID of the conversation to add the item to." + } + ], + "options": [ + { + "name": "include", + "description": "Additional fields to include in the response. See the `include`\nparameter for [listing Conversation items above](/docs/api-reference/conversations/list-items#conversations_list_items-include) for more information.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "file_search_call.results", + "web_search_call.results", + "web_search_call.action.sources", + "message.input_image.image_url", + "computer_call_output.output.image_url", + "code_interpreter_call.outputs", + "reasoning.encrypted_content", + "message.output_text.logprobs" + ], + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "items", + "group": "body", + "required": true, + "description": "The items to add to the conversation. You may add up to 20 items at a time.\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/conversations/{conversation_id}/items", + "operationId": "createConversationItems", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "conversation_id", + "from": "argument:conversation-id", + "required": true + }, + { + "in": "query", + "name": "include", + "from": "option:include" + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "items", + "from": "option:items", + "jsonPath": "items", + "encoding": "array", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/output.rs new file mode 100644 index 000000000..db0383888 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/output.rs @@ -0,0 +1,83 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("conversations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("items") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create items in a conversation with the given ID.") + .arg( + Arg::new("conversation-id") + .help("The ID of the conversation to add the item to.") + .required(true), + ) + .arg( + Arg::new("include") + .long("include") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Additional fields to include in the response. See the `include`\nparameter for [listing Conversation items above](/docs/api-reference/conversations/list-items#conversations_list_items-include) for more information.\n"), + ) + .arg( + Arg::new("items") + .long("items") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("The items to add to the conversation. You may add up to 20 items at a time.\n") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["conversations", "items", "create"] => handle_conversations_items_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_conversations_items_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let conversation_id = m.get_one::("conversation-id").map(String::as_str).unwrap_or(""); + let path = format!("/conversations/{}/items", runtime::path_escape(conversation_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(vs) = m.get_many::("include") { + query.push(("include", vs.cloned().collect::>().join(","))); + } + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("items") { + body.insert("items".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query, + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/recorded.json new file mode 100644 index 000000000..333ab6c72 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__create/recorded.json @@ -0,0 +1,20 @@ +{ + "args": [ + "conversations", + "items", + "create", + "EXAMPLE", + "--items", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/conversations/{conversation_id}/items", + "query": [], + "bodyFields": [ + "items" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/input.json new file mode 100644 index 000000000..130d4eca5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/input.json @@ -0,0 +1,73 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "conversations", + "commands": [ + { + "name": "items", + "commands": [ + { + "name": "delete", + "description": "Delete an item from a conversation with the given IDs.", + "arguments": [ + { + "name": "conversation-id", + "required": true, + "description": "The ID of the conversation that contains the item." + }, + { + "name": "item-id", + "required": true, + "description": "The ID of the item to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/conversations/{conversation_id}/items/{item_id}", + "operationId": "deleteConversationItem", + "params": [ + { + "in": "path", + "name": "conversation_id", + "from": "argument:conversation-id", + "required": true + }, + { + "in": "path", + "name": "item_id", + "from": "argument:item-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/output.rs new file mode 100644 index 000000000..9a53c51ba --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/output.rs @@ -0,0 +1,62 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("conversations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("items") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete an item from a conversation with the given IDs.") + .arg( + Arg::new("conversation-id") + .help("The ID of the conversation that contains the item.") + .required(true), + ) + .arg( + Arg::new("item-id") + .help("The ID of the item to delete.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["conversations", "items", "delete"] => handle_conversations_items_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_conversations_items_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let conversation_id = m.get_one::("conversation-id").map(String::as_str).unwrap_or(""); + let item_id = m.get_one::("item-id").map(String::as_str).unwrap_or(""); + let path = format!("/conversations/{}/items/{}", runtime::path_escape(conversation_id), runtime::path_escape(item_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/recorded.json new file mode 100644 index 000000000..5d60403f1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__delete/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "conversations", + "items", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/conversations/{conversation_id}/items/{item_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/input.json new file mode 100644 index 000000000..257a22456 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/input.json @@ -0,0 +1,141 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "conversations", + "commands": [ + { + "name": "items", + "commands": [ + { + "name": "list", + "description": "List all items for a conversation with the given ID.", + "arguments": [ + { + "name": "conversation-id", + "required": true, + "description": "The ID of the conversation to list items for." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between\n1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "The order to return the input items in. Default is `desc`.\n- `asc`: Return the input items in ascending order.\n- `desc`: Return the input items in descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "An item ID to list items after, used in pagination.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "include", + "description": "Specify additional output data to include in the model response. Currently supported values are:\n- `web_search_call.action.sources`: Include the sources of the web search tool call.\n- `code_interpreter_call.outputs`: Includes the outputs of python code execution in code interpreter tool call items.\n- `computer_call_output.output.image_url`: Include image urls from the computer call output.\n- `file_search_call.results`: Include the search results of the file search tool call.\n- `message.input_image.image_url`: Include image urls from the input message.\n- `message.output_text.logprobs`: Include logprobs with assistant messages.\n- `reasoning.encrypted_content`: Includes an encrypted version of reasoning tokens in reasoning item outputs. This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly (like when the `store` parameter is set to `false`, or when an organization is enrolled in the zero data retention program).", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "file_search_call.results", + "web_search_call.results", + "web_search_call.action.sources", + "message.input_image.image_url", + "computer_call_output.output.image_url", + "code_interpreter_call.outputs", + "reasoning.encrypted_content", + "message.output_text.logprobs" + ], + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/conversations/{conversation_id}/items", + "operationId": "listConversationItems", + "params": [ + { + "in": "path", + "name": "conversation_id", + "from": "argument:conversation-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "include", + "from": "option:include" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/output.rs new file mode 100644 index 000000000..98851e857 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/output.rs @@ -0,0 +1,92 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("conversations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("items") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List all items for a conversation with the given ID.") + .arg( + Arg::new("conversation-id") + .help("The ID of the conversation to list items for.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between\n1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("The order to return the input items in. Default is `desc`.\n- `asc`: Return the input items in ascending order.\n- `desc`: Return the input items in descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("An item ID to list items after, used in pagination.\n"), + ) + .arg( + Arg::new("include") + .long("include") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Specify additional output data to include in the model response. Currently supported values are:\n- `web_search_call.action.sources`: Include the sources of the web search tool call.\n- `code_interpreter_call.outputs`: Includes the outputs of python code execution in code interpreter tool call items.\n- `computer_call_output.output.image_url`: Include image urls from the computer call output.\n- `file_search_call.results`: Include the search results of the file search tool call.\n- `message.input_image.image_url`: Include image urls from the input message.\n- `message.output_text.logprobs`: Include logprobs with assistant messages.\n- `reasoning.encrypted_content`: Includes an encrypted version of reasoning tokens in reasoning item outputs. This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly (like when the `store` parameter is set to `false`, or when an organization is enrolled in the zero data retention program)."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["conversations", "items", "list"] => handle_conversations_items_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_conversations_items_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let conversation_id = m.get_one::("conversation-id").map(String::as_str).unwrap_or(""); + let path = format!("/conversations/{}/items", runtime::path_escape(conversation_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(vs) = m.get_many::("include") { + query.push(("include", vs.cloned().collect::>().join(","))); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/recorded.json new file mode 100644 index 000000000..028e2c441 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__list/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "conversations", + "items", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/conversations/{conversation_id}/items", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/input.json new file mode 100644 index 000000000..257a22456 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/input.json @@ -0,0 +1,141 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "conversations", + "commands": [ + { + "name": "items", + "commands": [ + { + "name": "list", + "description": "List all items for a conversation with the given ID.", + "arguments": [ + { + "name": "conversation-id", + "required": true, + "description": "The ID of the conversation to list items for." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between\n1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "The order to return the input items in. Default is `desc`.\n- `asc`: Return the input items in ascending order.\n- `desc`: Return the input items in descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "An item ID to list items after, used in pagination.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "include", + "description": "Specify additional output data to include in the model response. Currently supported values are:\n- `web_search_call.action.sources`: Include the sources of the web search tool call.\n- `code_interpreter_call.outputs`: Includes the outputs of python code execution in code interpreter tool call items.\n- `computer_call_output.output.image_url`: Include image urls from the computer call output.\n- `file_search_call.results`: Include the search results of the file search tool call.\n- `message.input_image.image_url`: Include image urls from the input message.\n- `message.output_text.logprobs`: Include logprobs with assistant messages.\n- `reasoning.encrypted_content`: Includes an encrypted version of reasoning tokens in reasoning item outputs. This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly (like when the `store` parameter is set to `false`, or when an organization is enrolled in the zero data retention program).", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "file_search_call.results", + "web_search_call.results", + "web_search_call.action.sources", + "message.input_image.image_url", + "computer_call_output.output.image_url", + "code_interpreter_call.outputs", + "reasoning.encrypted_content", + "message.output_text.logprobs" + ], + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/conversations/{conversation_id}/items", + "operationId": "listConversationItems", + "params": [ + { + "in": "path", + "name": "conversation_id", + "from": "argument:conversation-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "include", + "from": "option:include" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/output.rs new file mode 100644 index 000000000..98851e857 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/output.rs @@ -0,0 +1,92 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("conversations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("items") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List all items for a conversation with the given ID.") + .arg( + Arg::new("conversation-id") + .help("The ID of the conversation to list items for.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between\n1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("The order to return the input items in. Default is `desc`.\n- `asc`: Return the input items in ascending order.\n- `desc`: Return the input items in descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("An item ID to list items after, used in pagination.\n"), + ) + .arg( + Arg::new("include") + .long("include") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Specify additional output data to include in the model response. Currently supported values are:\n- `web_search_call.action.sources`: Include the sources of the web search tool call.\n- `code_interpreter_call.outputs`: Includes the outputs of python code execution in code interpreter tool call items.\n- `computer_call_output.output.image_url`: Include image urls from the computer call output.\n- `file_search_call.results`: Include the search results of the file search tool call.\n- `message.input_image.image_url`: Include image urls from the input message.\n- `message.output_text.logprobs`: Include logprobs with assistant messages.\n- `reasoning.encrypted_content`: Includes an encrypted version of reasoning tokens in reasoning item outputs. This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly (like when the `store` parameter is set to `false`, or when an organization is enrolled in the zero data retention program)."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["conversations", "items", "list"] => handle_conversations_items_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_conversations_items_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let conversation_id = m.get_one::("conversation-id").map(String::as_str).unwrap_or(""); + let path = format!("/conversations/{}/items", runtime::path_escape(conversation_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(vs) = m.get_many::("include") { + query.push(("include", vs.cloned().collect::>().join(","))); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/recorded.json new file mode 100644 index 000000000..028e2c441 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__items__retrieve/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "conversations", + "items", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/conversations/{conversation_id}/items", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/input.json new file mode 100644 index 000000000..6f97e8d23 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/input.json @@ -0,0 +1,60 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "conversations", + "commands": [ + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Get a conversation", + "arguments": [ + { + "name": "conversation-id", + "required": true, + "description": "The ID of the conversation to retrieve." + } + ], + "x-openapi": { + "method": "get", + "path": "/conversations/{conversation_id}", + "operationId": "getConversation", + "params": [ + { + "in": "path", + "name": "conversation_id", + "from": "argument:conversation-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/output.rs new file mode 100644 index 000000000..8cb8834c2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/output.rs @@ -0,0 +1,52 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("conversations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Get a conversation") + .arg( + Arg::new("conversation-id") + .help("The ID of the conversation to retrieve.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["conversations", "retrieve"] => handle_conversations_retrieve(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_conversations_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let conversation_id = m.get_one::("conversation-id").map(String::as_str).unwrap_or(""); + let path = format!("/conversations/{}", runtime::path_escape(conversation_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/recorded.json new file mode 100644 index 000000000..8d8fb41c5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "conversations", + "retrieve", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/conversations/{conversation_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/input.json new file mode 100644 index 000000000..3a36dcae8 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/input.json @@ -0,0 +1,81 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "conversations", + "commands": [ + { + "name": "create", + "description": "Create a conversation.", + "options": [ + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "items", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/conversations", + "operationId": "createConversation", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "items", + "from": "option:items", + "jsonPath": "items", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/output.rs new file mode 100644 index 000000000..14d5b0ddf --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/output.rs @@ -0,0 +1,68 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("conversations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a conversation.") + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("items") + .long("items"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["conversations", "create"] => handle_conversations_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_conversations_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/conversations".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("items") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("items".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/recorded.json new file mode 100644 index 000000000..31b2abc87 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/conversations__update/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "conversations", + "create" + ], + "request": { + "method": "post", + "path": "/conversations", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/input.json new file mode 100644 index 000000000..f8052ebb2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/input.json @@ -0,0 +1,139 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "embeddings", + "commands": [ + { + "name": "create", + "description": "Creates an embedding vector representing the input text.", + "options": [ + { + "name": "input", + "group": "body", + "required": true, + "description": "Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model (8192 tokens for all embedding models), cannot be an empty string, and any array must be 2048 dimensions or less. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens. In addition to the per-input token limit, all embedding models enforce a maximum of 300,000 tokens summed across all inputs in a single request.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "model", + "group": "body", + "required": true, + "description": "ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "encoding-format", + "group": "body", + "description": "The format to return the embeddings in. Can be either `float` or [`base64`](https://pypi.org/project/pybase64/).", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "float", + "base64" + ] + } + ] + }, + { + "name": "dimensions", + "group": "body", + "description": "The number of dimensions the resulting output embeddings should have. Only supported in `text-embedding-3` and later models.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "user", + "group": "body", + "description": "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices#end-user-ids).\n", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/embeddings", + "operationId": "createEmbedding", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "input", + "from": "option:input", + "jsonPath": "input", + "encoding": "json", + "required": true + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "encoding_format", + "from": "option:encoding-format", + "jsonPath": "encoding_format", + "encoding": "string" + }, + { + "name": "dimensions", + "from": "option:dimensions", + "jsonPath": "dimensions", + "encoding": "integer" + }, + { + "name": "user", + "from": "option:user", + "jsonPath": "user", + "encoding": "string" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/output.rs new file mode 100644 index 000000000..a31ddebd5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/output.rs @@ -0,0 +1,97 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("embeddings") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates an embedding vector representing the input text.") + .arg( + Arg::new("input") + .long("input") + .help("Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model (8192 tokens for all embedding models), cannot be an empty string, and any array must be 2048 dimensions or less. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens. In addition to the per-input token limit, all embedding models enforce a maximum of 300,000 tokens summed across all inputs in a single request.\n") + .required(true), + ) + .arg( + Arg::new("model") + .long("model") + .help("ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n") + .required(true), + ) + .arg( + Arg::new("encoding-format") + .long("encoding-format") + .help("The format to return the embeddings in. Can be either `float` or [`base64`](https://pypi.org/project/pybase64/)."), + ) + .arg( + Arg::new("dimensions") + .long("dimensions") + .value_parser(clap::value_parser!(i64)) + .help("The number of dimensions the resulting output embeddings should have. Only supported in `text-embedding-3` and later models.\n"), + ) + .arg( + Arg::new("user") + .long("user") + .help("A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices#end-user-ids).\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["embeddings", "create"] => handle_embeddings_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_embeddings_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/embeddings".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("input") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input".to_string(), value); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("encoding-format") { + body.insert("encoding_format".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("dimensions") { + body.insert("dimensions".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("user") { + body.insert("user".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/recorded.json new file mode 100644 index 000000000..567bb9d31 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/embeddings__create/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "embeddings", + "create", + "--input", + "EXAMPLE", + "--model", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/embeddings", + "query": [], + "bodyFields": [ + "input", + "model" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/input.json new file mode 100644 index 000000000..664da5d4a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/input.json @@ -0,0 +1,110 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "list", + "description": "Returns a list of files.", + "options": [ + { + "name": "purpose", + "description": "Only return files with the given purpose.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 10,000, and the default is 10,000.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/files", + "operationId": "listFiles", + "params": [ + { + "in": "query", + "name": "purpose", + "from": "option:purpose" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/output.rs new file mode 100644 index 000000000..51fb1887d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of files.") + .arg( + Arg::new("purpose") + .long("purpose") + .help("Only return files with the given purpose."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 10,000, and the default is 10,000.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["files", "list"] => handle_files_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_files_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/files".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("purpose") { + query.push(("purpose", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/recorded.json new file mode 100644 index 000000000..512d0a280 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__content/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "files", + "list" + ], + "request": { + "method": "get", + "path": "/files", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/input.json new file mode 100644 index 000000000..5dee90b99 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/input.json @@ -0,0 +1,111 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "create", + "description": "Upload a file that can be used across various endpoints. Individual files\ncan be up to 512 MB, and each project can store up to 2.5 TB of files in\ntotal. There is no organization-wide storage limit. Uploads to this\nendpoint are rate-limited to 1,000 requests per minute per authenticated\nuser.\n\n- The Assistants API supports files up to 2 million tokens and of specific\n file types. See the [Assistants Tools guide](/docs/assistants/tools) for\n details.\n- The Fine-tuning API only supports `.jsonl` files. The input also has\n certain required formats for fine-tuning\n [chat](/docs/api-reference/fine-tuning/chat-input) or\n [completions](/docs/api-reference/fine-tuning/completions-input) models.\n- The Batch API only supports `.jsonl` files up to 200 MB in size. The input\n also has a specific required\n [format](/docs/api-reference/batch/request-input).\n- For Retrieval or `file_search` ingestion, upload files here first. If\n you need to attach multiple uploaded files to the same vector store, use\n [`/vector_stores/{vector_store_id}/file_batches`](/docs/api-reference/vector-stores-file-batches/createBatch)\n instead of attaching them one by one. Vector store attachment has separate\n limits from file upload, including 2,000 attached files per minute per\n organization.\n\nPlease [contact us](https://help.openai.com/) if you need to increase these\nstorage limits.\n", + "options": [ + { + "name": "file", + "group": "body", + "required": true, + "description": "The File object (not file name) to be uploaded.\n", + "arguments": [ + { + "name": "path" + } + ] + }, + { + "name": "purpose", + "group": "body", + "required": true, + "description": "The intended purpose of the uploaded file. One of:\n- `assistants`: Used in the Assistants API\n- `batch`: Used in the Batch API\n- `fine-tune`: Used for fine-tuning\n- `vision`: Images used for vision fine-tuning\n- `user_data`: Flexible file type for any purpose\n- `evals`: Used for eval data sets\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "assistants", + "batch", + "fine-tune", + "vision", + "user_data", + "evals" + ] + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted.", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/files", + "operationId": "createFile", + "contentType": "multipart/form-data", + "body": { + "style": "multipart", + "contentType": "multipart/form-data", + "properties": [ + { + "name": "file", + "from": "option:file", + "jsonPath": "file", + "encoding": "file", + "required": true + }, + { + "name": "purpose", + "from": "option:purpose", + "jsonPath": "purpose", + "encoding": "string", + "required": true + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/output.rs new file mode 100644 index 000000000..95d3dccae --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/output.rs @@ -0,0 +1,78 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Upload a file that can be used across various endpoints. Individual files\ncan be up to 512 MB, and each project can store up to 2.5 TB of files in\ntotal. There is no organization-wide storage limit. Uploads to this\nendpoint are rate-limited to 1,000 requests per minute per authenticated\nuser.\n\n- The Assistants API supports files up to 2 million tokens and of specific\n file types. See the [Assistants Tools guide](/docs/assistants/tools) for\n details.\n- The Fine-tuning API only supports `.jsonl` files. The input also has\n certain required formats for fine-tuning\n [chat](/docs/api-reference/fine-tuning/chat-input) or\n [completions](/docs/api-reference/fine-tuning/completions-input) models.\n- The Batch API only supports `.jsonl` files up to 200 MB in size. The input\n also has a specific required\n [format](/docs/api-reference/batch/request-input).\n- For Retrieval or `file_search` ingestion, upload files here first. If\n you need to attach multiple uploaded files to the same vector store, use\n [`/vector_stores/{vector_store_id}/file_batches`](/docs/api-reference/vector-stores-file-batches/createBatch)\n instead of attaching them one by one. Vector store attachment has separate\n limits from file upload, including 2,000 attached files per minute per\n organization.\n\nPlease [contact us](https://help.openai.com/) if you need to increase these\nstorage limits.\n") + .arg( + Arg::new("file") + .long("file") + .help("The File object (not file name) to be uploaded.\n") + .required(true), + ) + .arg( + Arg::new("purpose") + .long("purpose") + .help("The intended purpose of the uploaded file. One of:\n- `assistants`: Used in the Assistants API\n- `batch`: Used in the Batch API\n- `fine-tune`: Used for fine-tuning\n- `vision`: Images used for vision fine-tuning\n- `user_data`: Flexible file type for any purpose\n- `evals`: Used for eval data sets\n") + .required(true), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["files", "create"] => handle_files_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_files_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/files".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("file") { + body.insert("file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("purpose") { + body.insert("purpose".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/recorded.json new file mode 100644 index 000000000..90f9dbb12 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__create/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "files", + "create", + "--file", + "EXAMPLE", + "--purpose", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/files", + "query": [], + "bodyFields": [ + "file", + "purpose" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/input.json new file mode 100644 index 000000000..44255d186 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "delete", + "description": "Delete a file and remove it from all vector stores.", + "arguments": [ + { + "name": "file-id", + "required": true, + "description": "The ID of the file to use for this request." + } + ], + "x-openapi": { + "method": "delete", + "path": "/files/{file_id}", + "operationId": "deleteFile", + "params": [ + { + "in": "path", + "name": "file_id", + "from": "argument:file-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/output.rs new file mode 100644 index 000000000..5cf3f62f5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a file and remove it from all vector stores.") + .arg( + Arg::new("file-id") + .help("The ID of the file to use for this request.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["files", "delete"] => handle_files_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_files_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let file_id = m.get_one::("file-id").map(String::as_str).unwrap_or(""); + let path = format!("/files/{}", runtime::path_escape(file_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/recorded.json new file mode 100644 index 000000000..21f4fb55e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "files", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/files/{file_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/input.json new file mode 100644 index 000000000..664da5d4a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/input.json @@ -0,0 +1,110 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "list", + "description": "Returns a list of files.", + "options": [ + { + "name": "purpose", + "description": "Only return files with the given purpose.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 10,000, and the default is 10,000.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/files", + "operationId": "listFiles", + "params": [ + { + "in": "query", + "name": "purpose", + "from": "option:purpose" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/output.rs new file mode 100644 index 000000000..51fb1887d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of files.") + .arg( + Arg::new("purpose") + .long("purpose") + .help("Only return files with the given purpose."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 10,000, and the default is 10,000.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["files", "list"] => handle_files_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_files_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/files".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("purpose") { + query.push(("purpose", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/recorded.json new file mode 100644 index 000000000..512d0a280 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__list/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "files", + "list" + ], + "request": { + "method": "get", + "path": "/files", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/input.json new file mode 100644 index 000000000..664da5d4a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/input.json @@ -0,0 +1,110 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "list", + "description": "Returns a list of files.", + "options": [ + { + "name": "purpose", + "description": "Only return files with the given purpose.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 10,000, and the default is 10,000.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/files", + "operationId": "listFiles", + "params": [ + { + "in": "query", + "name": "purpose", + "from": "option:purpose" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/output.rs new file mode 100644 index 000000000..51fb1887d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of files.") + .arg( + Arg::new("purpose") + .long("purpose") + .help("Only return files with the given purpose."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 10,000, and the default is 10,000.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["files", "list"] => handle_files_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_files_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/files".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("purpose") { + query.push(("purpose", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/recorded.json new file mode 100644 index 000000000..512d0a280 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/files__retrieve/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "files", + "list" + ], + "request": { + "method": "get", + "path": "/files", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/input.json new file mode 100644 index 000000000..2983cd878 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/input.json @@ -0,0 +1,97 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "checkpoints", + "commands": [ + { + "name": "permissions", + "commands": [ + { + "name": "create", + "description": "**NOTE:** Calling this endpoint requires an [admin API key](../admin-api-keys).\n\nThis enables organization owners to share fine-tuned models with other projects in their organization.\n", + "arguments": [ + { + "name": "fine-tuned-model-checkpoint", + "required": true, + "description": "The ID of the fine-tuned model checkpoint to create a permission for.\n" + } + ], + "options": [ + { + "name": "project-ids", + "group": "body", + "required": true, + "description": "The project identifiers to grant access to.", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + "operationId": "createFineTuningCheckpointPermission", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "fine_tuned_model_checkpoint", + "from": "argument:fine-tuned-model-checkpoint", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "project_ids", + "from": "option:project-ids", + "jsonPath": "project_ids", + "encoding": "array", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/output.rs new file mode 100644 index 000000000..6a2bf734e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/output.rs @@ -0,0 +1,77 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("checkpoints") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("permissions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("**NOTE:** Calling this endpoint requires an [admin API key](../admin-api-keys).\n\nThis enables organization owners to share fine-tuned models with other projects in their organization.\n") + .arg( + Arg::new("fine-tuned-model-checkpoint") + .help("The ID of the fine-tuned model checkpoint to create a permission for.\n") + .required(true), + ) + .arg( + Arg::new("project-ids") + .long("project-ids") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("The project identifiers to grant access to.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "checkpoints", "permissions", "create"] => handle_fine_tuning_checkpoints_permissions_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_checkpoints_permissions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let fine_tuned_model_checkpoint = m.get_one::("fine-tuned-model-checkpoint").map(String::as_str).unwrap_or(""); + let path = format!("/fine_tuning/checkpoints/{}/permissions", runtime::path_escape(fine_tuned_model_checkpoint)); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("project-ids") { + body.insert("project_ids".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/recorded.json new file mode 100644 index 000000000..1f512aeec --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__create/recorded.json @@ -0,0 +1,21 @@ +{ + "args": [ + "fine-tuning", + "checkpoints", + "permissions", + "create", + "EXAMPLE", + "--project-ids", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + "query": [], + "bodyFields": [ + "project_ids" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/input.json new file mode 100644 index 000000000..0219b04f6 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/input.json @@ -0,0 +1,78 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "checkpoints", + "commands": [ + { + "name": "permissions", + "commands": [ + { + "name": "delete", + "description": "**NOTE:** This endpoint requires an [admin API key](../admin-api-keys).\n\nOrganization owners can use this endpoint to delete a permission for a fine-tuned model checkpoint.\n", + "arguments": [ + { + "name": "fine-tuned-model-checkpoint", + "required": true, + "description": "The ID of the fine-tuned model checkpoint to delete a permission for.\n" + }, + { + "name": "permission-id", + "required": true, + "description": "The ID of the fine-tuned model checkpoint permission to delete.\n" + } + ], + "x-openapi": { + "method": "delete", + "path": "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions/{permission_id}", + "operationId": "deleteFineTuningCheckpointPermission", + "params": [ + { + "in": "path", + "name": "fine_tuned_model_checkpoint", + "from": "argument:fine-tuned-model-checkpoint", + "required": true + }, + { + "in": "path", + "name": "permission_id", + "from": "argument:permission-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/output.rs new file mode 100644 index 000000000..aa870065b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/output.rs @@ -0,0 +1,67 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("checkpoints") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("permissions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("**NOTE:** This endpoint requires an [admin API key](../admin-api-keys).\n\nOrganization owners can use this endpoint to delete a permission for a fine-tuned model checkpoint.\n") + .arg( + Arg::new("fine-tuned-model-checkpoint") + .help("The ID of the fine-tuned model checkpoint to delete a permission for.\n") + .required(true), + ) + .arg( + Arg::new("permission-id") + .help("The ID of the fine-tuned model checkpoint permission to delete.\n") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "checkpoints", "permissions", "delete"] => handle_fine_tuning_checkpoints_permissions_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_checkpoints_permissions_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let fine_tuned_model_checkpoint = m.get_one::("fine-tuned-model-checkpoint").map(String::as_str).unwrap_or(""); + let permission_id = m.get_one::("permission-id").map(String::as_str).unwrap_or(""); + let path = format!("/fine_tuning/checkpoints/{}/permissions/{}", runtime::path_escape(fine_tuned_model_checkpoint), runtime::path_escape(permission_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/recorded.json new file mode 100644 index 000000000..a8f024b2f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__delete/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "fine-tuning", + "checkpoints", + "permissions", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions/{permission_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/input.json new file mode 100644 index 000000000..f294fc0f7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/input.json @@ -0,0 +1,133 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "checkpoints", + "commands": [ + { + "name": "permissions", + "commands": [ + { + "name": "list", + "description": "**NOTE:** This endpoint requires an [admin API key](../admin-api-keys).\n\nOrganization owners can use this endpoint to view all permissions for a fine-tuned model checkpoint.\n", + "arguments": [ + { + "name": "fine-tuned-model-checkpoint", + "required": true, + "description": "The ID of the fine-tuned model checkpoint to get permissions for.\n" + } + ], + "options": [ + { + "name": "project-id", + "description": "The ID of the project to get permissions for.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "after", + "description": "Identifier for the last permission ID from the previous pagination request.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "Number of permissions to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "The order in which to retrieve permissions.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "ascending", + "descending" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + "operationId": "listFineTuningCheckpointPermissions", + "params": [ + { + "in": "path", + "name": "fine_tuned_model_checkpoint", + "from": "argument:fine-tuned-model-checkpoint", + "required": true + }, + { + "in": "query", + "name": "project_id", + "from": "option:project-id" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/output.rs new file mode 100644 index 000000000..447d54b46 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/output.rs @@ -0,0 +1,95 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("checkpoints") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("permissions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("**NOTE:** This endpoint requires an [admin API key](../admin-api-keys).\n\nOrganization owners can use this endpoint to view all permissions for a fine-tuned model checkpoint.\n") + .arg( + Arg::new("fine-tuned-model-checkpoint") + .help("The ID of the fine-tuned model checkpoint to get permissions for.\n") + .required(true), + ) + .arg( + Arg::new("project-id") + .long("project-id") + .help("The ID of the project to get permissions for."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last permission ID from the previous pagination request."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of permissions to retrieve."), + ) + .arg( + Arg::new("order") + .long("order") + .help("The order in which to retrieve permissions."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "checkpoints", "permissions", "list"] => handle_fine_tuning_checkpoints_permissions_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_checkpoints_permissions_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let fine_tuned_model_checkpoint = m.get_one::("fine-tuned-model-checkpoint").map(String::as_str).unwrap_or(""); + let path = format!("/fine_tuning/checkpoints/{}/permissions", runtime::path_escape(fine_tuned_model_checkpoint)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("project-id") { + query.push(("project_id", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/recorded.json new file mode 100644 index 000000000..be45e1b2d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__checkpoints__permissions__retrieve/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "fine-tuning", + "checkpoints", + "permissions", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/input.json new file mode 100644 index 000000000..6ac0e4e9c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/input.json @@ -0,0 +1,206 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "jobs", + "commands": [ + { + "name": "create", + "description": "Creates a fine-tuning job which begins the process of creating a new model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n[Learn more about fine-tuning](/docs/guides/model-optimization)\n", + "options": [ + { + "name": "model", + "group": "body", + "required": true, + "description": "The name of the model to fine-tune. You can select one of the\n[supported models](/docs/guides/fine-tuning#which-models-can-be-fine-tuned).\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "training-file", + "group": "body", + "required": true, + "description": "The ID of an uploaded file that contains training data.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`.\n\nThe contents of the file should differ depending on if the model uses the [chat](/docs/api-reference/fine-tuning/chat-input), [completions](/docs/api-reference/fine-tuning/completions-input) format, or if the fine-tuning method uses the [preference](/docs/api-reference/fine-tuning/preference-input) format.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "hyperparameters", + "group": "body", + "description": "The hyperparameters used for the fine-tuning job.\nThis value is now deprecated in favor of `method`, and should be passed in under the `method` parameter.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "suffix", + "group": "body", + "description": "A string of up to 64 characters that will be added to your fine-tuned model name.\n\nFor example, a `suffix` of \"custom-model-name\" would produce a model name like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "validation-file", + "group": "body", + "description": "The ID of an uploaded file that contains validation data.\n\nIf you provide this file, the data is used to generate validation\nmetrics periodically during fine-tuning. These metrics can be viewed in\nthe fine-tuning results file.\nThe same data should not be present in both train and validation files.\n\nYour dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "integrations", + "group": "body", + "description": "A list of integrations to enable for your fine-tuning job.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "seed", + "group": "body", + "description": "The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases.\nIf a seed is not specified, one will be generated for you.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "method", + "group": "body", + "description": "The method used for fine-tuning.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/fine_tuning/jobs", + "operationId": "createFineTuningJob", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "training_file", + "from": "option:training-file", + "jsonPath": "training_file", + "encoding": "string", + "required": true + }, + { + "name": "hyperparameters", + "from": "option:hyperparameters", + "jsonPath": "hyperparameters", + "encoding": "json" + }, + { + "name": "suffix", + "from": "option:suffix", + "jsonPath": "suffix", + "encoding": "string" + }, + { + "name": "validation_file", + "from": "option:validation-file", + "jsonPath": "validation_file", + "encoding": "string" + }, + { + "name": "integrations", + "from": "option:integrations", + "jsonPath": "integrations", + "encoding": "array" + }, + { + "name": "seed", + "from": "option:seed", + "jsonPath": "seed", + "encoding": "integer" + }, + { + "name": "method", + "from": "option:method", + "jsonPath": "method", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/output.rs new file mode 100644 index 000000000..575f58b85 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/output.rs @@ -0,0 +1,139 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("jobs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a fine-tuning job which begins the process of creating a new model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n[Learn more about fine-tuning](/docs/guides/model-optimization)\n") + .arg( + Arg::new("model") + .long("model") + .help("The name of the model to fine-tune. You can select one of the\n[supported models](/docs/guides/fine-tuning#which-models-can-be-fine-tuned).\n") + .required(true), + ) + .arg( + Arg::new("training-file") + .long("training-file") + .help("The ID of an uploaded file that contains training data.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`.\n\nThe contents of the file should differ depending on if the model uses the [chat](/docs/api-reference/fine-tuning/chat-input), [completions](/docs/api-reference/fine-tuning/completions-input) format, or if the fine-tuning method uses the [preference](/docs/api-reference/fine-tuning/preference-input) format.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n") + .required(true), + ) + .arg( + Arg::new("hyperparameters") + .long("hyperparameters") + .help("The hyperparameters used for the fine-tuning job.\nThis value is now deprecated in favor of `method`, and should be passed in under the `method` parameter.\n"), + ) + .arg( + Arg::new("suffix") + .long("suffix") + .help("A string of up to 64 characters that will be added to your fine-tuned model name.\n\nFor example, a `suffix` of \"custom-model-name\" would produce a model name like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`.\n"), + ) + .arg( + Arg::new("validation-file") + .long("validation-file") + .help("The ID of an uploaded file that contains validation data.\n\nIf you provide this file, the data is used to generate validation\nmetrics periodically during fine-tuning. These metrics can be viewed in\nthe fine-tuning results file.\nThe same data should not be present in both train and validation files.\n\nYour dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n"), + ) + .arg( + Arg::new("integrations") + .long("integrations") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of integrations to enable for your fine-tuning job."), + ) + .arg( + Arg::new("seed") + .long("seed") + .value_parser(clap::value_parser!(i64)) + .help("The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases.\nIf a seed is not specified, one will be generated for you.\n"), + ) + .arg( + Arg::new("method") + .long("method") + .help("The method used for fine-tuning."), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "jobs", "create"] => handle_fine_tuning_jobs_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_jobs_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/fine_tuning/jobs".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("training-file") { + body.insert("training_file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("hyperparameters") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("hyperparameters".to_string(), value); + } + if let Some(v) = m.get_one::("suffix") { + body.insert("suffix".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("validation-file") { + body.insert("validation_file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("integrations") { + body.insert("integrations".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("seed") { + body.insert("seed".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("method") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("method".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/recorded.json new file mode 100644 index 000000000..b9c984644 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__cancel/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "fine-tuning", + "jobs", + "create", + "--model", + "EXAMPLE", + "--training-file", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/fine_tuning/jobs", + "query": [], + "bodyFields": [ + "model", + "training_file" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/input.json new file mode 100644 index 000000000..6cc0b8ae0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "jobs", + "commands": [ + { + "name": "checkpoints", + "commands": [ + { + "name": "list", + "description": "List checkpoints for a fine-tuning job.\n", + "arguments": [ + { + "name": "fine-tuning-job-id", + "required": true, + "description": "The ID of the fine-tuning job to get checkpoints for.\n" + } + ], + "options": [ + { + "name": "after", + "description": "Identifier for the last checkpoint ID from the previous pagination request.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "Number of checkpoints to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/fine_tuning/jobs/{fine_tuning_job_id}/checkpoints", + "operationId": "listFineTuningJobCheckpoints", + "params": [ + { + "in": "path", + "name": "fine_tuning_job_id", + "from": "argument:fine-tuning-job-id", + "required": true + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/output.rs new file mode 100644 index 000000000..9b832cc04 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("jobs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("checkpoints") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List checkpoints for a fine-tuning job.\n") + .arg( + Arg::new("fine-tuning-job-id") + .help("The ID of the fine-tuning job to get checkpoints for.\n") + .required(true), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last checkpoint ID from the previous pagination request."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of checkpoints to retrieve."), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "jobs", "checkpoints", "list"] => handle_fine_tuning_jobs_checkpoints_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_jobs_checkpoints_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let fine_tuning_job_id = m.get_one::("fine-tuning-job-id").map(String::as_str).unwrap_or(""); + let path = format!("/fine_tuning/jobs/{}/checkpoints", runtime::path_escape(fine_tuning_job_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/recorded.json new file mode 100644 index 000000000..acc5c18a6 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__checkpoints__list/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "fine-tuning", + "jobs", + "checkpoints", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/fine_tuning/jobs/{fine_tuning_job_id}/checkpoints", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/input.json new file mode 100644 index 000000000..6ac0e4e9c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/input.json @@ -0,0 +1,206 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "jobs", + "commands": [ + { + "name": "create", + "description": "Creates a fine-tuning job which begins the process of creating a new model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n[Learn more about fine-tuning](/docs/guides/model-optimization)\n", + "options": [ + { + "name": "model", + "group": "body", + "required": true, + "description": "The name of the model to fine-tune. You can select one of the\n[supported models](/docs/guides/fine-tuning#which-models-can-be-fine-tuned).\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "training-file", + "group": "body", + "required": true, + "description": "The ID of an uploaded file that contains training data.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`.\n\nThe contents of the file should differ depending on if the model uses the [chat](/docs/api-reference/fine-tuning/chat-input), [completions](/docs/api-reference/fine-tuning/completions-input) format, or if the fine-tuning method uses the [preference](/docs/api-reference/fine-tuning/preference-input) format.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "hyperparameters", + "group": "body", + "description": "The hyperparameters used for the fine-tuning job.\nThis value is now deprecated in favor of `method`, and should be passed in under the `method` parameter.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "suffix", + "group": "body", + "description": "A string of up to 64 characters that will be added to your fine-tuned model name.\n\nFor example, a `suffix` of \"custom-model-name\" would produce a model name like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "validation-file", + "group": "body", + "description": "The ID of an uploaded file that contains validation data.\n\nIf you provide this file, the data is used to generate validation\nmetrics periodically during fine-tuning. These metrics can be viewed in\nthe fine-tuning results file.\nThe same data should not be present in both train and validation files.\n\nYour dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "integrations", + "group": "body", + "description": "A list of integrations to enable for your fine-tuning job.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "seed", + "group": "body", + "description": "The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases.\nIf a seed is not specified, one will be generated for you.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "method", + "group": "body", + "description": "The method used for fine-tuning.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/fine_tuning/jobs", + "operationId": "createFineTuningJob", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "training_file", + "from": "option:training-file", + "jsonPath": "training_file", + "encoding": "string", + "required": true + }, + { + "name": "hyperparameters", + "from": "option:hyperparameters", + "jsonPath": "hyperparameters", + "encoding": "json" + }, + { + "name": "suffix", + "from": "option:suffix", + "jsonPath": "suffix", + "encoding": "string" + }, + { + "name": "validation_file", + "from": "option:validation-file", + "jsonPath": "validation_file", + "encoding": "string" + }, + { + "name": "integrations", + "from": "option:integrations", + "jsonPath": "integrations", + "encoding": "array" + }, + { + "name": "seed", + "from": "option:seed", + "jsonPath": "seed", + "encoding": "integer" + }, + { + "name": "method", + "from": "option:method", + "jsonPath": "method", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/output.rs new file mode 100644 index 000000000..575f58b85 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/output.rs @@ -0,0 +1,139 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("jobs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a fine-tuning job which begins the process of creating a new model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n[Learn more about fine-tuning](/docs/guides/model-optimization)\n") + .arg( + Arg::new("model") + .long("model") + .help("The name of the model to fine-tune. You can select one of the\n[supported models](/docs/guides/fine-tuning#which-models-can-be-fine-tuned).\n") + .required(true), + ) + .arg( + Arg::new("training-file") + .long("training-file") + .help("The ID of an uploaded file that contains training data.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`.\n\nThe contents of the file should differ depending on if the model uses the [chat](/docs/api-reference/fine-tuning/chat-input), [completions](/docs/api-reference/fine-tuning/completions-input) format, or if the fine-tuning method uses the [preference](/docs/api-reference/fine-tuning/preference-input) format.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n") + .required(true), + ) + .arg( + Arg::new("hyperparameters") + .long("hyperparameters") + .help("The hyperparameters used for the fine-tuning job.\nThis value is now deprecated in favor of `method`, and should be passed in under the `method` parameter.\n"), + ) + .arg( + Arg::new("suffix") + .long("suffix") + .help("A string of up to 64 characters that will be added to your fine-tuned model name.\n\nFor example, a `suffix` of \"custom-model-name\" would produce a model name like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`.\n"), + ) + .arg( + Arg::new("validation-file") + .long("validation-file") + .help("The ID of an uploaded file that contains validation data.\n\nIf you provide this file, the data is used to generate validation\nmetrics periodically during fine-tuning. These metrics can be viewed in\nthe fine-tuning results file.\nThe same data should not be present in both train and validation files.\n\nYour dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n"), + ) + .arg( + Arg::new("integrations") + .long("integrations") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of integrations to enable for your fine-tuning job."), + ) + .arg( + Arg::new("seed") + .long("seed") + .value_parser(clap::value_parser!(i64)) + .help("The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases.\nIf a seed is not specified, one will be generated for you.\n"), + ) + .arg( + Arg::new("method") + .long("method") + .help("The method used for fine-tuning."), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "jobs", "create"] => handle_fine_tuning_jobs_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_jobs_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/fine_tuning/jobs".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("training-file") { + body.insert("training_file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("hyperparameters") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("hyperparameters".to_string(), value); + } + if let Some(v) = m.get_one::("suffix") { + body.insert("suffix".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("validation-file") { + body.insert("validation_file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("integrations") { + body.insert("integrations".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("seed") { + body.insert("seed".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("method") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("method".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/recorded.json new file mode 100644 index 000000000..b9c984644 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__create/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "fine-tuning", + "jobs", + "create", + "--model", + "EXAMPLE", + "--training-file", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/fine_tuning/jobs", + "query": [], + "bodyFields": [ + "model", + "training_file" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/input.json new file mode 100644 index 000000000..560329ed9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/input.json @@ -0,0 +1,98 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "jobs", + "commands": [ + { + "name": "list", + "description": "List your organization's fine-tuning jobs\n", + "options": [ + { + "name": "after", + "description": "Identifier for the last job from the previous pagination request.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "Number of fine-tuning jobs to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "metadata", + "description": "Optional metadata filter. To filter, use the syntax `metadata[k]=v`. Alternatively, set `metadata=null` to indicate no metadata.\n", + "group": "query", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/fine_tuning/jobs", + "operationId": "listPaginatedFineTuningJobs", + "params": [ + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "metadata", + "from": "option:metadata", + "explode": true, + "style": "deepObject" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/output.rs new file mode 100644 index 000000000..51ad2720a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("jobs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List your organization's fine-tuning jobs\n") + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last job from the previous pagination request."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of fine-tuning jobs to retrieve."), + ) + .arg( + Arg::new("metadata") + .long("metadata") + .help("Optional metadata filter. To filter, use the syntax `metadata[k]=v`. Alternatively, set `metadata=null` to indicate no metadata.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "jobs", "list"] => handle_fine_tuning_jobs_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_jobs_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/fine_tuning/jobs".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("metadata") { + query.push(("metadata", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/recorded.json new file mode 100644 index 000000000..63867398a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list-events/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "fine-tuning", + "jobs", + "list" + ], + "request": { + "method": "get", + "path": "/fine_tuning/jobs", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/input.json new file mode 100644 index 000000000..560329ed9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/input.json @@ -0,0 +1,98 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "jobs", + "commands": [ + { + "name": "list", + "description": "List your organization's fine-tuning jobs\n", + "options": [ + { + "name": "after", + "description": "Identifier for the last job from the previous pagination request.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "Number of fine-tuning jobs to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "metadata", + "description": "Optional metadata filter. To filter, use the syntax `metadata[k]=v`. Alternatively, set `metadata=null` to indicate no metadata.\n", + "group": "query", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/fine_tuning/jobs", + "operationId": "listPaginatedFineTuningJobs", + "params": [ + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "metadata", + "from": "option:metadata", + "explode": true, + "style": "deepObject" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/output.rs new file mode 100644 index 000000000..51ad2720a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("jobs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List your organization's fine-tuning jobs\n") + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last job from the previous pagination request."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of fine-tuning jobs to retrieve."), + ) + .arg( + Arg::new("metadata") + .long("metadata") + .help("Optional metadata filter. To filter, use the syntax `metadata[k]=v`. Alternatively, set `metadata=null` to indicate no metadata.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "jobs", "list"] => handle_fine_tuning_jobs_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_jobs_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/fine_tuning/jobs".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("metadata") { + query.push(("metadata", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/recorded.json new file mode 100644 index 000000000..63867398a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__list/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "fine-tuning", + "jobs", + "list" + ], + "request": { + "method": "get", + "path": "/fine_tuning/jobs", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/input.json new file mode 100644 index 000000000..6ac0e4e9c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/input.json @@ -0,0 +1,206 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "jobs", + "commands": [ + { + "name": "create", + "description": "Creates a fine-tuning job which begins the process of creating a new model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n[Learn more about fine-tuning](/docs/guides/model-optimization)\n", + "options": [ + { + "name": "model", + "group": "body", + "required": true, + "description": "The name of the model to fine-tune. You can select one of the\n[supported models](/docs/guides/fine-tuning#which-models-can-be-fine-tuned).\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "training-file", + "group": "body", + "required": true, + "description": "The ID of an uploaded file that contains training data.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`.\n\nThe contents of the file should differ depending on if the model uses the [chat](/docs/api-reference/fine-tuning/chat-input), [completions](/docs/api-reference/fine-tuning/completions-input) format, or if the fine-tuning method uses the [preference](/docs/api-reference/fine-tuning/preference-input) format.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "hyperparameters", + "group": "body", + "description": "The hyperparameters used for the fine-tuning job.\nThis value is now deprecated in favor of `method`, and should be passed in under the `method` parameter.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "suffix", + "group": "body", + "description": "A string of up to 64 characters that will be added to your fine-tuned model name.\n\nFor example, a `suffix` of \"custom-model-name\" would produce a model name like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "validation-file", + "group": "body", + "description": "The ID of an uploaded file that contains validation data.\n\nIf you provide this file, the data is used to generate validation\nmetrics periodically during fine-tuning. These metrics can be viewed in\nthe fine-tuning results file.\nThe same data should not be present in both train and validation files.\n\nYour dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "integrations", + "group": "body", + "description": "A list of integrations to enable for your fine-tuning job.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "seed", + "group": "body", + "description": "The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases.\nIf a seed is not specified, one will be generated for you.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "method", + "group": "body", + "description": "The method used for fine-tuning.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/fine_tuning/jobs", + "operationId": "createFineTuningJob", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "training_file", + "from": "option:training-file", + "jsonPath": "training_file", + "encoding": "string", + "required": true + }, + { + "name": "hyperparameters", + "from": "option:hyperparameters", + "jsonPath": "hyperparameters", + "encoding": "json" + }, + { + "name": "suffix", + "from": "option:suffix", + "jsonPath": "suffix", + "encoding": "string" + }, + { + "name": "validation_file", + "from": "option:validation-file", + "jsonPath": "validation_file", + "encoding": "string" + }, + { + "name": "integrations", + "from": "option:integrations", + "jsonPath": "integrations", + "encoding": "array" + }, + { + "name": "seed", + "from": "option:seed", + "jsonPath": "seed", + "encoding": "integer" + }, + { + "name": "method", + "from": "option:method", + "jsonPath": "method", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/output.rs new file mode 100644 index 000000000..575f58b85 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/output.rs @@ -0,0 +1,139 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("jobs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a fine-tuning job which begins the process of creating a new model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n[Learn more about fine-tuning](/docs/guides/model-optimization)\n") + .arg( + Arg::new("model") + .long("model") + .help("The name of the model to fine-tune. You can select one of the\n[supported models](/docs/guides/fine-tuning#which-models-can-be-fine-tuned).\n") + .required(true), + ) + .arg( + Arg::new("training-file") + .long("training-file") + .help("The ID of an uploaded file that contains training data.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`.\n\nThe contents of the file should differ depending on if the model uses the [chat](/docs/api-reference/fine-tuning/chat-input), [completions](/docs/api-reference/fine-tuning/completions-input) format, or if the fine-tuning method uses the [preference](/docs/api-reference/fine-tuning/preference-input) format.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n") + .required(true), + ) + .arg( + Arg::new("hyperparameters") + .long("hyperparameters") + .help("The hyperparameters used for the fine-tuning job.\nThis value is now deprecated in favor of `method`, and should be passed in under the `method` parameter.\n"), + ) + .arg( + Arg::new("suffix") + .long("suffix") + .help("A string of up to 64 characters that will be added to your fine-tuned model name.\n\nFor example, a `suffix` of \"custom-model-name\" would produce a model name like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`.\n"), + ) + .arg( + Arg::new("validation-file") + .long("validation-file") + .help("The ID of an uploaded file that contains validation data.\n\nIf you provide this file, the data is used to generate validation\nmetrics periodically during fine-tuning. These metrics can be viewed in\nthe fine-tuning results file.\nThe same data should not be present in both train and validation files.\n\nYour dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n"), + ) + .arg( + Arg::new("integrations") + .long("integrations") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of integrations to enable for your fine-tuning job."), + ) + .arg( + Arg::new("seed") + .long("seed") + .value_parser(clap::value_parser!(i64)) + .help("The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases.\nIf a seed is not specified, one will be generated for you.\n"), + ) + .arg( + Arg::new("method") + .long("method") + .help("The method used for fine-tuning."), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "jobs", "create"] => handle_fine_tuning_jobs_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_jobs_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/fine_tuning/jobs".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("training-file") { + body.insert("training_file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("hyperparameters") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("hyperparameters".to_string(), value); + } + if let Some(v) = m.get_one::("suffix") { + body.insert("suffix".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("validation-file") { + body.insert("validation_file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("integrations") { + body.insert("integrations".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("seed") { + body.insert("seed".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("method") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("method".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/recorded.json new file mode 100644 index 000000000..b9c984644 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__pause/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "fine-tuning", + "jobs", + "create", + "--model", + "EXAMPLE", + "--training-file", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/fine_tuning/jobs", + "query": [], + "bodyFields": [ + "model", + "training_file" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/input.json new file mode 100644 index 000000000..6ac0e4e9c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/input.json @@ -0,0 +1,206 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "jobs", + "commands": [ + { + "name": "create", + "description": "Creates a fine-tuning job which begins the process of creating a new model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n[Learn more about fine-tuning](/docs/guides/model-optimization)\n", + "options": [ + { + "name": "model", + "group": "body", + "required": true, + "description": "The name of the model to fine-tune. You can select one of the\n[supported models](/docs/guides/fine-tuning#which-models-can-be-fine-tuned).\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "training-file", + "group": "body", + "required": true, + "description": "The ID of an uploaded file that contains training data.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`.\n\nThe contents of the file should differ depending on if the model uses the [chat](/docs/api-reference/fine-tuning/chat-input), [completions](/docs/api-reference/fine-tuning/completions-input) format, or if the fine-tuning method uses the [preference](/docs/api-reference/fine-tuning/preference-input) format.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "hyperparameters", + "group": "body", + "description": "The hyperparameters used for the fine-tuning job.\nThis value is now deprecated in favor of `method`, and should be passed in under the `method` parameter.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "suffix", + "group": "body", + "description": "A string of up to 64 characters that will be added to your fine-tuned model name.\n\nFor example, a `suffix` of \"custom-model-name\" would produce a model name like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "validation-file", + "group": "body", + "description": "The ID of an uploaded file that contains validation data.\n\nIf you provide this file, the data is used to generate validation\nmetrics periodically during fine-tuning. These metrics can be viewed in\nthe fine-tuning results file.\nThe same data should not be present in both train and validation files.\n\nYour dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "integrations", + "group": "body", + "description": "A list of integrations to enable for your fine-tuning job.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "seed", + "group": "body", + "description": "The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases.\nIf a seed is not specified, one will be generated for you.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "method", + "group": "body", + "description": "The method used for fine-tuning.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/fine_tuning/jobs", + "operationId": "createFineTuningJob", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json", + "required": true + }, + { + "name": "training_file", + "from": "option:training-file", + "jsonPath": "training_file", + "encoding": "string", + "required": true + }, + { + "name": "hyperparameters", + "from": "option:hyperparameters", + "jsonPath": "hyperparameters", + "encoding": "json" + }, + { + "name": "suffix", + "from": "option:suffix", + "jsonPath": "suffix", + "encoding": "string" + }, + { + "name": "validation_file", + "from": "option:validation-file", + "jsonPath": "validation_file", + "encoding": "string" + }, + { + "name": "integrations", + "from": "option:integrations", + "jsonPath": "integrations", + "encoding": "array" + }, + { + "name": "seed", + "from": "option:seed", + "jsonPath": "seed", + "encoding": "integer" + }, + { + "name": "method", + "from": "option:method", + "jsonPath": "method", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/output.rs new file mode 100644 index 000000000..575f58b85 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/output.rs @@ -0,0 +1,139 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("jobs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a fine-tuning job which begins the process of creating a new model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n[Learn more about fine-tuning](/docs/guides/model-optimization)\n") + .arg( + Arg::new("model") + .long("model") + .help("The name of the model to fine-tune. You can select one of the\n[supported models](/docs/guides/fine-tuning#which-models-can-be-fine-tuned).\n") + .required(true), + ) + .arg( + Arg::new("training-file") + .long("training-file") + .help("The ID of an uploaded file that contains training data.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`.\n\nThe contents of the file should differ depending on if the model uses the [chat](/docs/api-reference/fine-tuning/chat-input), [completions](/docs/api-reference/fine-tuning/completions-input) format, or if the fine-tuning method uses the [preference](/docs/api-reference/fine-tuning/preference-input) format.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n") + .required(true), + ) + .arg( + Arg::new("hyperparameters") + .long("hyperparameters") + .help("The hyperparameters used for the fine-tuning job.\nThis value is now deprecated in favor of `method`, and should be passed in under the `method` parameter.\n"), + ) + .arg( + Arg::new("suffix") + .long("suffix") + .help("A string of up to 64 characters that will be added to your fine-tuned model name.\n\nFor example, a `suffix` of \"custom-model-name\" would produce a model name like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`.\n"), + ) + .arg( + Arg::new("validation-file") + .long("validation-file") + .help("The ID of an uploaded file that contains validation data.\n\nIf you provide this file, the data is used to generate validation\nmetrics periodically during fine-tuning. These metrics can be viewed in\nthe fine-tuning results file.\nThe same data should not be present in both train and validation files.\n\nYour dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n"), + ) + .arg( + Arg::new("integrations") + .long("integrations") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of integrations to enable for your fine-tuning job."), + ) + .arg( + Arg::new("seed") + .long("seed") + .value_parser(clap::value_parser!(i64)) + .help("The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases.\nIf a seed is not specified, one will be generated for you.\n"), + ) + .arg( + Arg::new("method") + .long("method") + .help("The method used for fine-tuning."), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "jobs", "create"] => handle_fine_tuning_jobs_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_jobs_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/fine_tuning/jobs".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("training-file") { + body.insert("training_file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("hyperparameters") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("hyperparameters".to_string(), value); + } + if let Some(v) = m.get_one::("suffix") { + body.insert("suffix".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("validation-file") { + body.insert("validation_file".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("integrations") { + body.insert("integrations".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("seed") { + body.insert("seed".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("method") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("method".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/recorded.json new file mode 100644 index 000000000..b9c984644 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__resume/recorded.json @@ -0,0 +1,22 @@ +{ + "args": [ + "fine-tuning", + "jobs", + "create", + "--model", + "EXAMPLE", + "--training-file", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/fine_tuning/jobs", + "query": [], + "bodyFields": [ + "model", + "training_file" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/input.json new file mode 100644 index 000000000..560329ed9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/input.json @@ -0,0 +1,98 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "fine-tuning", + "commands": [ + { + "name": "jobs", + "commands": [ + { + "name": "list", + "description": "List your organization's fine-tuning jobs\n", + "options": [ + { + "name": "after", + "description": "Identifier for the last job from the previous pagination request.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "limit", + "description": "Number of fine-tuning jobs to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "metadata", + "description": "Optional metadata filter. To filter, use the syntax `metadata[k]=v`. Alternatively, set `metadata=null` to indicate no metadata.\n", + "group": "query", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/fine_tuning/jobs", + "operationId": "listPaginatedFineTuningJobs", + "params": [ + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "metadata", + "from": "option:metadata", + "explode": true, + "style": "deepObject" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/output.rs new file mode 100644 index 000000000..51ad2720a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/output.rs @@ -0,0 +1,76 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("fine-tuning") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("jobs") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List your organization's fine-tuning jobs\n") + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last job from the previous pagination request."), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of fine-tuning jobs to retrieve."), + ) + .arg( + Arg::new("metadata") + .long("metadata") + .help("Optional metadata filter. To filter, use the syntax `metadata[k]=v`. Alternatively, set `metadata=null` to indicate no metadata.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["fine-tuning", "jobs", "list"] => handle_fine_tuning_jobs_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_fine_tuning_jobs_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/fine_tuning/jobs".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("metadata") { + query.push(("metadata", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/recorded.json new file mode 100644 index 000000000..63867398a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/fine-tuning__jobs__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "fine-tuning", + "jobs", + "list" + ], + "request": { + "method": "get", + "path": "/fine_tuning/jobs", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/input.json new file mode 100644 index 000000000..703e7fdf8 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "models", + "commands": [ + { + "name": "delete", + "description": "Delete a fine-tuned model. You must have the Owner role in your organization to delete a model.", + "arguments": [ + { + "name": "model", + "required": true, + "description": "The model to delete" + } + ], + "x-openapi": { + "method": "delete", + "path": "/models/{model}", + "operationId": "deleteModel", + "params": [ + { + "in": "path", + "name": "model", + "from": "argument:model", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/output.rs new file mode 100644 index 000000000..7235dfe8c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("models") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a fine-tuned model. You must have the Owner role in your organization to delete a model.") + .arg( + Arg::new("model") + .help("The model to delete") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["models", "delete"] => handle_models_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_models_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let model = m.get_one::("model").map(String::as_str).unwrap_or(""); + let path = format!("/models/{}", runtime::path_escape(model)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/recorded.json new file mode 100644 index 000000000..16a1fdd2c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "models", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/models/{model}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/input.json new file mode 100644 index 000000000..76e1391af --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/input.json @@ -0,0 +1,42 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "models", + "commands": [ + { + "name": "list", + "description": "Lists the currently available models, and provides basic information about each one such as the owner and availability.", + "x-openapi": { + "method": "get", + "path": "/models", + "operationId": "listModels" + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/output.rs new file mode 100644 index 000000000..5209bcb9c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/output.rs @@ -0,0 +1,45 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("models") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the currently available models, and provides basic information about each one such as the owner and availability."), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["models", "list"] => handle_models_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_models_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/models".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/recorded.json new file mode 100644 index 000000000..23bd8686b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__list/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "models", + "list" + ], + "request": { + "method": "get", + "path": "/models", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/input.json new file mode 100644 index 000000000..76e1391af --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/input.json @@ -0,0 +1,42 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "models", + "commands": [ + { + "name": "list", + "description": "Lists the currently available models, and provides basic information about each one such as the owner and availability.", + "x-openapi": { + "method": "get", + "path": "/models", + "operationId": "listModels" + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/output.rs new file mode 100644 index 000000000..5209bcb9c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/output.rs @@ -0,0 +1,45 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("models") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the currently available models, and provides basic information about each one such as the owner and availability."), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["models", "list"] => handle_models_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_models_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/models".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/recorded.json new file mode 100644 index 000000000..23bd8686b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/models__retrieve/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "models", + "list" + ], + "request": { + "method": "get", + "path": "/models", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/input.json new file mode 100644 index 000000000..cea24fff0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/input.json @@ -0,0 +1,85 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "moderations", + "commands": [ + { + "name": "create", + "description": "Classifies if text and/or image inputs are potentially harmful. Learn\nmore in the [moderation guide](/docs/guides/moderation).\n", + "options": [ + { + "name": "input", + "group": "body", + "required": true, + "description": "Input (or inputs) to classify. Can be a single string, an array of strings, or\nan array of multi-modal input objects similar to other models.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "model", + "group": "body", + "description": "The content moderation model you would like to use. Learn more in\n[the moderation guide](/docs/guides/moderation), and learn about\navailable models [here](/docs/models#moderation).\n", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/moderations", + "operationId": "createModeration", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "input", + "from": "option:input", + "jsonPath": "input", + "encoding": "json", + "required": true + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/output.rs new file mode 100644 index 000000000..cc5a75473 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/output.rs @@ -0,0 +1,71 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("moderations") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Classifies if text and/or image inputs are potentially harmful. Learn\nmore in the [moderation guide](/docs/guides/moderation).\n") + .arg( + Arg::new("input") + .long("input") + .help("Input (or inputs) to classify. Can be a single string, an array of strings, or\nan array of multi-modal input objects similar to other models.\n") + .required(true), + ) + .arg( + Arg::new("model") + .long("model") + .help("The content moderation model you would like to use. Learn more in\n[the moderation guide](/docs/guides/moderation), and learn about\navailable models [here](/docs/models#moderation).\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["moderations", "create"] => handle_moderations_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_moderations_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/moderations".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("input") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input".to_string(), value); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/recorded.json new file mode 100644 index 000000000..cbcf8c8dd --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/moderations__create/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "moderations", + "create", + "--input", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/moderations", + "query": [], + "bodyFields": [ + "input" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/input.json new file mode 100644 index 000000000..336d17ec4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/input.json @@ -0,0 +1,90 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "realtime", + "commands": [ + { + "name": "calls", + "commands": [ + { + "name": "create", + "description": "Create a new Realtime API call over WebRTC and receive the SDP answer needed\nto complete the peer connection.", + "options": [ + { + "name": "sdp", + "group": "body", + "required": true, + "description": "WebRTC Session Description Protocol (SDP) offer generated by the caller.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "session", + "group": "body", + "description": "Optional session configuration to apply before the realtime session is\ncreated. Use the same parameters you would send in a [`create client secret`](/docs/api-reference/realtime-sessions/create-realtime-client-secret)\nrequest.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/realtime/calls", + "operationId": "create-realtime-call", + "contentType": "multipart/form-data", + "body": { + "style": "multipart", + "contentType": "multipart/form-data", + "properties": [ + { + "name": "sdp", + "from": "option:sdp", + "jsonPath": "sdp", + "encoding": "string", + "required": true + }, + { + "name": "session", + "from": "option:session", + "jsonPath": "session", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/output.rs new file mode 100644 index 000000000..dbf7d0cd2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/output.rs @@ -0,0 +1,74 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("realtime") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("calls") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new Realtime API call over WebRTC and receive the SDP answer needed\nto complete the peer connection.") + .arg( + Arg::new("sdp") + .long("sdp") + .help("WebRTC Session Description Protocol (SDP) offer generated by the caller.") + .required(true), + ) + .arg( + Arg::new("session") + .long("session") + .help("Optional session configuration to apply before the realtime session is\ncreated. Use the same parameters you would send in a [`create client secret`](/docs/api-reference/realtime-sessions/create-realtime-client-secret)\nrequest."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["realtime", "calls", "create"] => handle_realtime_calls_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_realtime_calls_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/realtime/calls".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("sdp") { + body.insert("sdp".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("session") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("session".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/recorded.json new file mode 100644 index 000000000..a2fa24422 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__accept/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "realtime", + "calls", + "create", + "--sdp", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/realtime/calls", + "query": [], + "bodyFields": [ + "sdp" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/input.json new file mode 100644 index 000000000..336d17ec4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/input.json @@ -0,0 +1,90 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "realtime", + "commands": [ + { + "name": "calls", + "commands": [ + { + "name": "create", + "description": "Create a new Realtime API call over WebRTC and receive the SDP answer needed\nto complete the peer connection.", + "options": [ + { + "name": "sdp", + "group": "body", + "required": true, + "description": "WebRTC Session Description Protocol (SDP) offer generated by the caller.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "session", + "group": "body", + "description": "Optional session configuration to apply before the realtime session is\ncreated. Use the same parameters you would send in a [`create client secret`](/docs/api-reference/realtime-sessions/create-realtime-client-secret)\nrequest.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/realtime/calls", + "operationId": "create-realtime-call", + "contentType": "multipart/form-data", + "body": { + "style": "multipart", + "contentType": "multipart/form-data", + "properties": [ + { + "name": "sdp", + "from": "option:sdp", + "jsonPath": "sdp", + "encoding": "string", + "required": true + }, + { + "name": "session", + "from": "option:session", + "jsonPath": "session", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/output.rs new file mode 100644 index 000000000..dbf7d0cd2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/output.rs @@ -0,0 +1,74 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("realtime") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("calls") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new Realtime API call over WebRTC and receive the SDP answer needed\nto complete the peer connection.") + .arg( + Arg::new("sdp") + .long("sdp") + .help("WebRTC Session Description Protocol (SDP) offer generated by the caller.") + .required(true), + ) + .arg( + Arg::new("session") + .long("session") + .help("Optional session configuration to apply before the realtime session is\ncreated. Use the same parameters you would send in a [`create client secret`](/docs/api-reference/realtime-sessions/create-realtime-client-secret)\nrequest."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["realtime", "calls", "create"] => handle_realtime_calls_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_realtime_calls_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/realtime/calls".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("sdp") { + body.insert("sdp".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("session") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("session".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/recorded.json new file mode 100644 index 000000000..a2fa24422 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__hangup/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "realtime", + "calls", + "create", + "--sdp", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/realtime/calls", + "query": [], + "bodyFields": [ + "sdp" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/input.json new file mode 100644 index 000000000..336d17ec4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/input.json @@ -0,0 +1,90 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "realtime", + "commands": [ + { + "name": "calls", + "commands": [ + { + "name": "create", + "description": "Create a new Realtime API call over WebRTC and receive the SDP answer needed\nto complete the peer connection.", + "options": [ + { + "name": "sdp", + "group": "body", + "required": true, + "description": "WebRTC Session Description Protocol (SDP) offer generated by the caller.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "session", + "group": "body", + "description": "Optional session configuration to apply before the realtime session is\ncreated. Use the same parameters you would send in a [`create client secret`](/docs/api-reference/realtime-sessions/create-realtime-client-secret)\nrequest.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/realtime/calls", + "operationId": "create-realtime-call", + "contentType": "multipart/form-data", + "body": { + "style": "multipart", + "contentType": "multipart/form-data", + "properties": [ + { + "name": "sdp", + "from": "option:sdp", + "jsonPath": "sdp", + "encoding": "string", + "required": true + }, + { + "name": "session", + "from": "option:session", + "jsonPath": "session", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/output.rs new file mode 100644 index 000000000..dbf7d0cd2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/output.rs @@ -0,0 +1,74 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("realtime") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("calls") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new Realtime API call over WebRTC and receive the SDP answer needed\nto complete the peer connection.") + .arg( + Arg::new("sdp") + .long("sdp") + .help("WebRTC Session Description Protocol (SDP) offer generated by the caller.") + .required(true), + ) + .arg( + Arg::new("session") + .long("session") + .help("Optional session configuration to apply before the realtime session is\ncreated. Use the same parameters you would send in a [`create client secret`](/docs/api-reference/realtime-sessions/create-realtime-client-secret)\nrequest."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["realtime", "calls", "create"] => handle_realtime_calls_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_realtime_calls_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/realtime/calls".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("sdp") { + body.insert("sdp".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("session") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("session".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/recorded.json new file mode 100644 index 000000000..a2fa24422 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__refer/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "realtime", + "calls", + "create", + "--sdp", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/realtime/calls", + "query": [], + "bodyFields": [ + "sdp" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/input.json new file mode 100644 index 000000000..336d17ec4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/input.json @@ -0,0 +1,90 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "realtime", + "commands": [ + { + "name": "calls", + "commands": [ + { + "name": "create", + "description": "Create a new Realtime API call over WebRTC and receive the SDP answer needed\nto complete the peer connection.", + "options": [ + { + "name": "sdp", + "group": "body", + "required": true, + "description": "WebRTC Session Description Protocol (SDP) offer generated by the caller.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "session", + "group": "body", + "description": "Optional session configuration to apply before the realtime session is\ncreated. Use the same parameters you would send in a [`create client secret`](/docs/api-reference/realtime-sessions/create-realtime-client-secret)\nrequest.", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/realtime/calls", + "operationId": "create-realtime-call", + "contentType": "multipart/form-data", + "body": { + "style": "multipart", + "contentType": "multipart/form-data", + "properties": [ + { + "name": "sdp", + "from": "option:sdp", + "jsonPath": "sdp", + "encoding": "string", + "required": true + }, + { + "name": "session", + "from": "option:session", + "jsonPath": "session", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/output.rs new file mode 100644 index 000000000..dbf7d0cd2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/output.rs @@ -0,0 +1,74 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("realtime") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("calls") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new Realtime API call over WebRTC and receive the SDP answer needed\nto complete the peer connection.") + .arg( + Arg::new("sdp") + .long("sdp") + .help("WebRTC Session Description Protocol (SDP) offer generated by the caller.") + .required(true), + ) + .arg( + Arg::new("session") + .long("session") + .help("Optional session configuration to apply before the realtime session is\ncreated. Use the same parameters you would send in a [`create client secret`](/docs/api-reference/realtime-sessions/create-realtime-client-secret)\nrequest."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["realtime", "calls", "create"] => handle_realtime_calls_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_realtime_calls_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/realtime/calls".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("sdp") { + body.insert("sdp".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("session") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("session".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/recorded.json new file mode 100644 index 000000000..a2fa24422 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__calls__reject/recorded.json @@ -0,0 +1,19 @@ +{ + "args": [ + "realtime", + "calls", + "create", + "--sdp", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/realtime/calls", + "query": [], + "bodyFields": [ + "sdp" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/input.json new file mode 100644 index 000000000..d3a6baca7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/input.json @@ -0,0 +1,88 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "realtime", + "commands": [ + { + "name": "client-secrets", + "commands": [ + { + "name": "create", + "description": "Create a Realtime client secret with an associated session configuration.\n\nClient secrets are short-lived tokens that can be passed to a client app,\nsuch as a web frontend or mobile client, which grants access to the Realtime API without\nleaking your main API key. You can configure a custom TTL for each client secret.\n\nYou can also attach session configuration options to the client secret, which will be\napplied to any sessions created using that client secret, but these can also be overridden\nby the client connection.\n\n[Learn more about authentication with client secrets over WebRTC](/docs/guides/realtime-webrtc).\n\nReturns the created client secret and the effective session object. The client secret is a string that looks like `ek_1234`.\n", + "options": [ + { + "name": "expires-after", + "group": "body", + "description": "Configuration for the client secret expiration. Expiration refers to the time after which\na client secret will no longer be valid for creating sessions. The session itself may\ncontinue after that time once started. A secret can be used to create multiple sessions\nuntil it expires.\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "session", + "group": "body", + "description": "Session configuration to use for the client secret. Choose either a realtime\nsession or a transcription session.\n", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/realtime/client_secrets", + "operationId": "create-realtime-client-secret", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + }, + { + "name": "session", + "from": "option:session", + "jsonPath": "session", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/output.rs new file mode 100644 index 000000000..241becfa7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/output.rs @@ -0,0 +1,75 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("realtime") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("client-secrets") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a Realtime client secret with an associated session configuration.\n\nClient secrets are short-lived tokens that can be passed to a client app,\nsuch as a web frontend or mobile client, which grants access to the Realtime API without\nleaking your main API key. You can configure a custom TTL for each client secret.\n\nYou can also attach session configuration options to the client secret, which will be\napplied to any sessions created using that client secret, but these can also be overridden\nby the client connection.\n\n[Learn more about authentication with client secrets over WebRTC](/docs/guides/realtime-webrtc).\n\nReturns the created client secret and the effective session object. The client secret is a string that looks like `ek_1234`.\n") + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("Configuration for the client secret expiration. Expiration refers to the time after which\na client secret will no longer be valid for creating sessions. The session itself may\ncontinue after that time once started. A secret can be used to create multiple sessions\nuntil it expires.\n"), + ) + .arg( + Arg::new("session") + .long("session") + .help("Session configuration to use for the client secret. Choose either a realtime\nsession or a transcription session.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["realtime", "client-secrets", "create"] => handle_realtime_client_secrets_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_realtime_client_secrets_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/realtime/client_secrets".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + if let Some(v) = m.get_one::("session") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("session".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/recorded.json new file mode 100644 index 000000000..b6f5bae3b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/realtime__client-secrets__create/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "realtime", + "client-secrets", + "create" + ], + "request": { + "method": "post", + "path": "/realtime/client_secrets", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/input.json new file mode 100644 index 000000000..9baac191a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/input.json @@ -0,0 +1,498 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "responses", + "commands": [ + { + "name": "create", + "description": "Creates a model response. Provide [text](/docs/guides/text) or\n[image](/docs/guides/images) inputs to generate [text](/docs/guides/text)\nor [JSON](/docs/guides/structured-outputs) outputs. Have the model call\nyour own [custom code](/docs/guides/function-calling) or use built-in\n[tools](/docs/guides/tools) like [web search](/docs/guides/tools-web-search)\nor [file search](/docs/guides/tools-file-search) to use your own data\nas input for the model's response.\n", + "options": [ + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-logprobs", + "group": "body", + "description": "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "temperature", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-p", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "user", + "group": "body", + "description": "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "safety-identifier", + "group": "body", + "description": "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "prompt-cache-key", + "group": "body", + "description": "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "service-tier", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt-cache-retention", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "previous-response-id", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "model", + "group": "body", + "description": "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "reasoning", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "background", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "max-tool-calls", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "text", + "group": "body", + "description": "Configuration options for a text response from the model. Can be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Structured Outputs](/docs/guides/structured-outputs)\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "description": "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "truncation", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "input", + "group": "body", + "description": "Text, image, or file inputs to the model, used to generate a response.\n\nLearn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Image inputs](/docs/guides/images)\n- [File inputs](/docs/guides/pdf-files)\n- [Conversation state](/docs/guides/conversation-state)\n- [Function calling](/docs/guides/function-calling)\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "include", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "store", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "instructions", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "stream", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "stream-options", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "conversation", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "context-management", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "max-output-tokens", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/responses", + "operationId": "createResponse", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "top_logprobs", + "from": "option:top-logprobs", + "jsonPath": "top_logprobs", + "encoding": "integer" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "json" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "json" + }, + { + "name": "user", + "from": "option:user", + "jsonPath": "user", + "encoding": "string" + }, + { + "name": "safety_identifier", + "from": "option:safety-identifier", + "jsonPath": "safety_identifier", + "encoding": "string" + }, + { + "name": "prompt_cache_key", + "from": "option:prompt-cache-key", + "jsonPath": "prompt_cache_key", + "encoding": "string" + }, + { + "name": "service_tier", + "from": "option:service-tier", + "jsonPath": "service_tier", + "encoding": "json" + }, + { + "name": "prompt_cache_retention", + "from": "option:prompt-cache-retention", + "jsonPath": "prompt_cache_retention", + "encoding": "json" + }, + { + "name": "previous_response_id", + "from": "option:previous-response-id", + "jsonPath": "previous_response_id", + "encoding": "json" + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "reasoning", + "from": "option:reasoning", + "jsonPath": "reasoning", + "encoding": "json" + }, + { + "name": "background", + "from": "option:background", + "jsonPath": "background", + "encoding": "json" + }, + { + "name": "max_tool_calls", + "from": "option:max-tool-calls", + "jsonPath": "max_tool_calls", + "encoding": "json" + }, + { + "name": "text", + "from": "option:text", + "jsonPath": "text", + "encoding": "json" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "json" + }, + { + "name": "truncation", + "from": "option:truncation", + "jsonPath": "truncation", + "encoding": "json" + }, + { + "name": "input", + "from": "option:input", + "jsonPath": "input", + "encoding": "json" + }, + { + "name": "include", + "from": "option:include", + "jsonPath": "include", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "json" + }, + { + "name": "store", + "from": "option:store", + "jsonPath": "store", + "encoding": "json" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "json" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "json" + }, + { + "name": "stream_options", + "from": "option:stream-options", + "jsonPath": "stream_options", + "encoding": "json" + }, + { + "name": "conversation", + "from": "option:conversation", + "jsonPath": "conversation", + "encoding": "json" + }, + { + "name": "context_management", + "from": "option:context-management", + "jsonPath": "context_management", + "encoding": "json" + }, + { + "name": "max_output_tokens", + "from": "option:max-output-tokens", + "jsonPath": "max_output_tokens", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/output.rs new file mode 100644 index 000000000..8b0ba14e3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/output.rs @@ -0,0 +1,313 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("responses") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a model response. Provide [text](/docs/guides/text) or\n[image](/docs/guides/images) inputs to generate [text](/docs/guides/text)\nor [JSON](/docs/guides/structured-outputs) outputs. Have the model call\nyour own [custom code](/docs/guides/function-calling) or use built-in\n[tools](/docs/guides/tools) like [web search](/docs/guides/tools-web-search)\nor [file search](/docs/guides/tools-file-search) to use your own data\nas input for the model's response.\n") + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("top-logprobs") + .long("top-logprobs") + .value_parser(clap::value_parser!(i64)) + .help("An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n"), + ) + .arg( + Arg::new("temperature") + .long("temperature"), + ) + .arg( + Arg::new("top-p") + .long("top-p"), + ) + .arg( + Arg::new("user") + .long("user") + .help("This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("safety-identifier") + .long("safety-identifier") + .help("A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("prompt-cache-key") + .long("prompt-cache-key") + .help("Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n"), + ) + .arg( + Arg::new("service-tier") + .long("service-tier"), + ) + .arg( + Arg::new("prompt-cache-retention") + .long("prompt-cache-retention"), + ) + .arg( + Arg::new("previous-response-id") + .long("previous-response-id"), + ) + .arg( + Arg::new("model") + .long("model") + .help("Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n"), + ) + .arg( + Arg::new("reasoning") + .long("reasoning"), + ) + .arg( + Arg::new("background") + .long("background"), + ) + .arg( + Arg::new("max-tool-calls") + .long("max-tool-calls"), + ) + .arg( + Arg::new("text") + .long("text") + .help("Configuration options for a text response from the model. Can be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Structured Outputs](/docs/guides/structured-outputs)\n"), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice") + .help("How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n"), + ) + .arg( + Arg::new("prompt") + .long("prompt"), + ) + .arg( + Arg::new("truncation") + .long("truncation"), + ) + .arg( + Arg::new("input") + .long("input") + .help("Text, image, or file inputs to the model, used to generate a response.\n\nLearn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Image inputs](/docs/guides/images)\n- [File inputs](/docs/guides/pdf-files)\n- [Conversation state](/docs/guides/conversation-state)\n- [Function calling](/docs/guides/function-calling)\n"), + ) + .arg( + Arg::new("include") + .long("include"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls"), + ) + .arg( + Arg::new("store") + .long("store"), + ) + .arg( + Arg::new("instructions") + .long("instructions"), + ) + .arg( + Arg::new("stream") + .long("stream"), + ) + .arg( + Arg::new("stream-options") + .long("stream-options"), + ) + .arg( + Arg::new("conversation") + .long("conversation"), + ) + .arg( + Arg::new("context-management") + .long("context-management"), + ) + .arg( + Arg::new("max-output-tokens") + .long("max-output-tokens"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["responses", "create"] => handle_responses_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_responses_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/responses".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("top-logprobs") { + body.insert("top_logprobs".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("temperature") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("temperature".to_string(), value); + } + if let Some(v) = m.get_one::("top-p") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("top_p".to_string(), value); + } + if let Some(v) = m.get_one::("user") { + body.insert("user".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("safety-identifier") { + body.insert("safety_identifier".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("prompt-cache-key") { + body.insert("prompt_cache_key".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("service-tier") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("service_tier".to_string(), value); + } + if let Some(v) = m.get_one::("prompt-cache-retention") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prompt_cache_retention".to_string(), value); + } + if let Some(v) = m.get_one::("previous-response-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("previous_response_id".to_string(), value); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("reasoning") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("reasoning".to_string(), value); + } + if let Some(v) = m.get_one::("background") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("background".to_string(), value); + } + if let Some(v) = m.get_one::("max-tool-calls") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("max_tool_calls".to_string(), value); + } + if let Some(v) = m.get_one::("text") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("text".to_string(), value); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prompt".to_string(), value); + } + if let Some(v) = m.get_one::("truncation") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("truncation".to_string(), value); + } + if let Some(v) = m.get_one::("input") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input".to_string(), value); + } + if let Some(v) = m.get_one::("include") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("include".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("parallel_tool_calls".to_string(), value); + } + if let Some(v) = m.get_one::("store") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("store".to_string(), value); + } + if let Some(v) = m.get_one::("instructions") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("instructions".to_string(), value); + } + if let Some(v) = m.get_one::("stream") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream".to_string(), value); + } + if let Some(v) = m.get_one::("stream-options") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream_options".to_string(), value); + } + if let Some(v) = m.get_one::("conversation") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("conversation".to_string(), value); + } + if let Some(v) = m.get_one::("context-management") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("context_management".to_string(), value); + } + if let Some(v) = m.get_one::("max-output-tokens") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("max_output_tokens".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/recorded.json new file mode 100644 index 000000000..bd75cb4cf --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__cancel/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "responses", + "create" + ], + "request": { + "method": "post", + "path": "/responses", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/input.json new file mode 100644 index 000000000..9baac191a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/input.json @@ -0,0 +1,498 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "responses", + "commands": [ + { + "name": "create", + "description": "Creates a model response. Provide [text](/docs/guides/text) or\n[image](/docs/guides/images) inputs to generate [text](/docs/guides/text)\nor [JSON](/docs/guides/structured-outputs) outputs. Have the model call\nyour own [custom code](/docs/guides/function-calling) or use built-in\n[tools](/docs/guides/tools) like [web search](/docs/guides/tools-web-search)\nor [file search](/docs/guides/tools-file-search) to use your own data\nas input for the model's response.\n", + "options": [ + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-logprobs", + "group": "body", + "description": "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "temperature", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-p", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "user", + "group": "body", + "description": "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "safety-identifier", + "group": "body", + "description": "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "prompt-cache-key", + "group": "body", + "description": "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "service-tier", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt-cache-retention", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "previous-response-id", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "model", + "group": "body", + "description": "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "reasoning", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "background", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "max-tool-calls", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "text", + "group": "body", + "description": "Configuration options for a text response from the model. Can be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Structured Outputs](/docs/guides/structured-outputs)\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "description": "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "truncation", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "input", + "group": "body", + "description": "Text, image, or file inputs to the model, used to generate a response.\n\nLearn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Image inputs](/docs/guides/images)\n- [File inputs](/docs/guides/pdf-files)\n- [Conversation state](/docs/guides/conversation-state)\n- [Function calling](/docs/guides/function-calling)\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "include", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "store", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "instructions", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "stream", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "stream-options", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "conversation", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "context-management", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "max-output-tokens", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/responses", + "operationId": "createResponse", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "top_logprobs", + "from": "option:top-logprobs", + "jsonPath": "top_logprobs", + "encoding": "integer" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "json" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "json" + }, + { + "name": "user", + "from": "option:user", + "jsonPath": "user", + "encoding": "string" + }, + { + "name": "safety_identifier", + "from": "option:safety-identifier", + "jsonPath": "safety_identifier", + "encoding": "string" + }, + { + "name": "prompt_cache_key", + "from": "option:prompt-cache-key", + "jsonPath": "prompt_cache_key", + "encoding": "string" + }, + { + "name": "service_tier", + "from": "option:service-tier", + "jsonPath": "service_tier", + "encoding": "json" + }, + { + "name": "prompt_cache_retention", + "from": "option:prompt-cache-retention", + "jsonPath": "prompt_cache_retention", + "encoding": "json" + }, + { + "name": "previous_response_id", + "from": "option:previous-response-id", + "jsonPath": "previous_response_id", + "encoding": "json" + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "reasoning", + "from": "option:reasoning", + "jsonPath": "reasoning", + "encoding": "json" + }, + { + "name": "background", + "from": "option:background", + "jsonPath": "background", + "encoding": "json" + }, + { + "name": "max_tool_calls", + "from": "option:max-tool-calls", + "jsonPath": "max_tool_calls", + "encoding": "json" + }, + { + "name": "text", + "from": "option:text", + "jsonPath": "text", + "encoding": "json" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "json" + }, + { + "name": "truncation", + "from": "option:truncation", + "jsonPath": "truncation", + "encoding": "json" + }, + { + "name": "input", + "from": "option:input", + "jsonPath": "input", + "encoding": "json" + }, + { + "name": "include", + "from": "option:include", + "jsonPath": "include", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "json" + }, + { + "name": "store", + "from": "option:store", + "jsonPath": "store", + "encoding": "json" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "json" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "json" + }, + { + "name": "stream_options", + "from": "option:stream-options", + "jsonPath": "stream_options", + "encoding": "json" + }, + { + "name": "conversation", + "from": "option:conversation", + "jsonPath": "conversation", + "encoding": "json" + }, + { + "name": "context_management", + "from": "option:context-management", + "jsonPath": "context_management", + "encoding": "json" + }, + { + "name": "max_output_tokens", + "from": "option:max-output-tokens", + "jsonPath": "max_output_tokens", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/output.rs new file mode 100644 index 000000000..8b0ba14e3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/output.rs @@ -0,0 +1,313 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("responses") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a model response. Provide [text](/docs/guides/text) or\n[image](/docs/guides/images) inputs to generate [text](/docs/guides/text)\nor [JSON](/docs/guides/structured-outputs) outputs. Have the model call\nyour own [custom code](/docs/guides/function-calling) or use built-in\n[tools](/docs/guides/tools) like [web search](/docs/guides/tools-web-search)\nor [file search](/docs/guides/tools-file-search) to use your own data\nas input for the model's response.\n") + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("top-logprobs") + .long("top-logprobs") + .value_parser(clap::value_parser!(i64)) + .help("An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n"), + ) + .arg( + Arg::new("temperature") + .long("temperature"), + ) + .arg( + Arg::new("top-p") + .long("top-p"), + ) + .arg( + Arg::new("user") + .long("user") + .help("This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("safety-identifier") + .long("safety-identifier") + .help("A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("prompt-cache-key") + .long("prompt-cache-key") + .help("Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n"), + ) + .arg( + Arg::new("service-tier") + .long("service-tier"), + ) + .arg( + Arg::new("prompt-cache-retention") + .long("prompt-cache-retention"), + ) + .arg( + Arg::new("previous-response-id") + .long("previous-response-id"), + ) + .arg( + Arg::new("model") + .long("model") + .help("Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n"), + ) + .arg( + Arg::new("reasoning") + .long("reasoning"), + ) + .arg( + Arg::new("background") + .long("background"), + ) + .arg( + Arg::new("max-tool-calls") + .long("max-tool-calls"), + ) + .arg( + Arg::new("text") + .long("text") + .help("Configuration options for a text response from the model. Can be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Structured Outputs](/docs/guides/structured-outputs)\n"), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice") + .help("How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n"), + ) + .arg( + Arg::new("prompt") + .long("prompt"), + ) + .arg( + Arg::new("truncation") + .long("truncation"), + ) + .arg( + Arg::new("input") + .long("input") + .help("Text, image, or file inputs to the model, used to generate a response.\n\nLearn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Image inputs](/docs/guides/images)\n- [File inputs](/docs/guides/pdf-files)\n- [Conversation state](/docs/guides/conversation-state)\n- [Function calling](/docs/guides/function-calling)\n"), + ) + .arg( + Arg::new("include") + .long("include"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls"), + ) + .arg( + Arg::new("store") + .long("store"), + ) + .arg( + Arg::new("instructions") + .long("instructions"), + ) + .arg( + Arg::new("stream") + .long("stream"), + ) + .arg( + Arg::new("stream-options") + .long("stream-options"), + ) + .arg( + Arg::new("conversation") + .long("conversation"), + ) + .arg( + Arg::new("context-management") + .long("context-management"), + ) + .arg( + Arg::new("max-output-tokens") + .long("max-output-tokens"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["responses", "create"] => handle_responses_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_responses_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/responses".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("top-logprobs") { + body.insert("top_logprobs".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("temperature") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("temperature".to_string(), value); + } + if let Some(v) = m.get_one::("top-p") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("top_p".to_string(), value); + } + if let Some(v) = m.get_one::("user") { + body.insert("user".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("safety-identifier") { + body.insert("safety_identifier".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("prompt-cache-key") { + body.insert("prompt_cache_key".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("service-tier") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("service_tier".to_string(), value); + } + if let Some(v) = m.get_one::("prompt-cache-retention") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prompt_cache_retention".to_string(), value); + } + if let Some(v) = m.get_one::("previous-response-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("previous_response_id".to_string(), value); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("reasoning") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("reasoning".to_string(), value); + } + if let Some(v) = m.get_one::("background") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("background".to_string(), value); + } + if let Some(v) = m.get_one::("max-tool-calls") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("max_tool_calls".to_string(), value); + } + if let Some(v) = m.get_one::("text") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("text".to_string(), value); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prompt".to_string(), value); + } + if let Some(v) = m.get_one::("truncation") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("truncation".to_string(), value); + } + if let Some(v) = m.get_one::("input") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input".to_string(), value); + } + if let Some(v) = m.get_one::("include") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("include".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("parallel_tool_calls".to_string(), value); + } + if let Some(v) = m.get_one::("store") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("store".to_string(), value); + } + if let Some(v) = m.get_one::("instructions") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("instructions".to_string(), value); + } + if let Some(v) = m.get_one::("stream") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream".to_string(), value); + } + if let Some(v) = m.get_one::("stream-options") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream_options".to_string(), value); + } + if let Some(v) = m.get_one::("conversation") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("conversation".to_string(), value); + } + if let Some(v) = m.get_one::("context-management") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("context_management".to_string(), value); + } + if let Some(v) = m.get_one::("max-output-tokens") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("max_output_tokens".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/recorded.json new file mode 100644 index 000000000..bd75cb4cf --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__compact/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "responses", + "create" + ], + "request": { + "method": "post", + "path": "/responses", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/input.json new file mode 100644 index 000000000..9baac191a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/input.json @@ -0,0 +1,498 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "responses", + "commands": [ + { + "name": "create", + "description": "Creates a model response. Provide [text](/docs/guides/text) or\n[image](/docs/guides/images) inputs to generate [text](/docs/guides/text)\nor [JSON](/docs/guides/structured-outputs) outputs. Have the model call\nyour own [custom code](/docs/guides/function-calling) or use built-in\n[tools](/docs/guides/tools) like [web search](/docs/guides/tools-web-search)\nor [file search](/docs/guides/tools-file-search) to use your own data\nas input for the model's response.\n", + "options": [ + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-logprobs", + "group": "body", + "description": "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "temperature", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "top-p", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "user", + "group": "body", + "description": "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "safety-identifier", + "group": "body", + "description": "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "prompt-cache-key", + "group": "body", + "description": "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "service-tier", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt-cache-retention", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "previous-response-id", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "model", + "group": "body", + "description": "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "reasoning", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "background", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "max-tool-calls", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "text", + "group": "body", + "description": "Configuration options for a text response from the model. Can be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Structured Outputs](/docs/guides/structured-outputs)\n", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "tools", + "group": "body", + "description": "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n", + "arguments": [ + { + "name": "value", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "description": "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "truncation", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "input", + "group": "body", + "description": "Text, image, or file inputs to the model, used to generate a response.\n\nLearn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Image inputs](/docs/guides/images)\n- [File inputs](/docs/guides/pdf-files)\n- [Conversation state](/docs/guides/conversation-state)\n- [Function calling](/docs/guides/function-calling)\n", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "include", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "store", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "instructions", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "stream", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "stream-options", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "conversation", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "context-management", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "max-output-tokens", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/responses", + "operationId": "createResponse", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + }, + { + "name": "top_logprobs", + "from": "option:top-logprobs", + "jsonPath": "top_logprobs", + "encoding": "integer" + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "json" + }, + { + "name": "top_p", + "from": "option:top-p", + "jsonPath": "top_p", + "encoding": "json" + }, + { + "name": "user", + "from": "option:user", + "jsonPath": "user", + "encoding": "string" + }, + { + "name": "safety_identifier", + "from": "option:safety-identifier", + "jsonPath": "safety_identifier", + "encoding": "string" + }, + { + "name": "prompt_cache_key", + "from": "option:prompt-cache-key", + "jsonPath": "prompt_cache_key", + "encoding": "string" + }, + { + "name": "service_tier", + "from": "option:service-tier", + "jsonPath": "service_tier", + "encoding": "json" + }, + { + "name": "prompt_cache_retention", + "from": "option:prompt-cache-retention", + "jsonPath": "prompt_cache_retention", + "encoding": "json" + }, + { + "name": "previous_response_id", + "from": "option:previous-response-id", + "jsonPath": "previous_response_id", + "encoding": "json" + }, + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "reasoning", + "from": "option:reasoning", + "jsonPath": "reasoning", + "encoding": "json" + }, + { + "name": "background", + "from": "option:background", + "jsonPath": "background", + "encoding": "json" + }, + { + "name": "max_tool_calls", + "from": "option:max-tool-calls", + "jsonPath": "max_tool_calls", + "encoding": "json" + }, + { + "name": "text", + "from": "option:text", + "jsonPath": "text", + "encoding": "json" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "array" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "json" + }, + { + "name": "truncation", + "from": "option:truncation", + "jsonPath": "truncation", + "encoding": "json" + }, + { + "name": "input", + "from": "option:input", + "jsonPath": "input", + "encoding": "json" + }, + { + "name": "include", + "from": "option:include", + "jsonPath": "include", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "json" + }, + { + "name": "store", + "from": "option:store", + "jsonPath": "store", + "encoding": "json" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "json" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "json" + }, + { + "name": "stream_options", + "from": "option:stream-options", + "jsonPath": "stream_options", + "encoding": "json" + }, + { + "name": "conversation", + "from": "option:conversation", + "jsonPath": "conversation", + "encoding": "json" + }, + { + "name": "context_management", + "from": "option:context-management", + "jsonPath": "context_management", + "encoding": "json" + }, + { + "name": "max_output_tokens", + "from": "option:max-output-tokens", + "jsonPath": "max_output_tokens", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/output.rs new file mode 100644 index 000000000..8b0ba14e3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/output.rs @@ -0,0 +1,313 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("responses") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a model response. Provide [text](/docs/guides/text) or\n[image](/docs/guides/images) inputs to generate [text](/docs/guides/text)\nor [JSON](/docs/guides/structured-outputs) outputs. Have the model call\nyour own [custom code](/docs/guides/function-calling) or use built-in\n[tools](/docs/guides/tools) like [web search](/docs/guides/tools-web-search)\nor [file search](/docs/guides/tools-file-search) to use your own data\nas input for the model's response.\n") + .arg( + Arg::new("metadata") + .long("metadata"), + ) + .arg( + Arg::new("top-logprobs") + .long("top-logprobs") + .value_parser(clap::value_parser!(i64)) + .help("An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n"), + ) + .arg( + Arg::new("temperature") + .long("temperature"), + ) + .arg( + Arg::new("top-p") + .long("top-p"), + ) + .arg( + Arg::new("user") + .long("user") + .help("This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("safety-identifier") + .long("safety-identifier") + .help("A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n"), + ) + .arg( + Arg::new("prompt-cache-key") + .long("prompt-cache-key") + .help("Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n"), + ) + .arg( + Arg::new("service-tier") + .long("service-tier"), + ) + .arg( + Arg::new("prompt-cache-retention") + .long("prompt-cache-retention"), + ) + .arg( + Arg::new("previous-response-id") + .long("previous-response-id"), + ) + .arg( + Arg::new("model") + .long("model") + .help("Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n"), + ) + .arg( + Arg::new("reasoning") + .long("reasoning"), + ) + .arg( + Arg::new("background") + .long("background"), + ) + .arg( + Arg::new("max-tool-calls") + .long("max-tool-calls"), + ) + .arg( + Arg::new("text") + .long("text") + .help("Configuration options for a text response from the model. Can be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Structured Outputs](/docs/guides/structured-outputs)\n"), + ) + .arg( + Arg::new("tools") + .long("tools") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice") + .help("How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n"), + ) + .arg( + Arg::new("prompt") + .long("prompt"), + ) + .arg( + Arg::new("truncation") + .long("truncation"), + ) + .arg( + Arg::new("input") + .long("input") + .help("Text, image, or file inputs to the model, used to generate a response.\n\nLearn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Image inputs](/docs/guides/images)\n- [File inputs](/docs/guides/pdf-files)\n- [Conversation state](/docs/guides/conversation-state)\n- [Function calling](/docs/guides/function-calling)\n"), + ) + .arg( + Arg::new("include") + .long("include"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls"), + ) + .arg( + Arg::new("store") + .long("store"), + ) + .arg( + Arg::new("instructions") + .long("instructions"), + ) + .arg( + Arg::new("stream") + .long("stream"), + ) + .arg( + Arg::new("stream-options") + .long("stream-options"), + ) + .arg( + Arg::new("conversation") + .long("conversation"), + ) + .arg( + Arg::new("context-management") + .long("context-management"), + ) + .arg( + Arg::new("max-output-tokens") + .long("max-output-tokens"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["responses", "create"] => handle_responses_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_responses_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/responses".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + if let Some(v) = m.get_one::("top-logprobs") { + body.insert("top_logprobs".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("temperature") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("temperature".to_string(), value); + } + if let Some(v) = m.get_one::("top-p") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("top_p".to_string(), value); + } + if let Some(v) = m.get_one::("user") { + body.insert("user".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("safety-identifier") { + body.insert("safety_identifier".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("prompt-cache-key") { + body.insert("prompt_cache_key".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("service-tier") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("service_tier".to_string(), value); + } + if let Some(v) = m.get_one::("prompt-cache-retention") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prompt_cache_retention".to_string(), value); + } + if let Some(v) = m.get_one::("previous-response-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("previous_response_id".to_string(), value); + } + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("reasoning") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("reasoning".to_string(), value); + } + if let Some(v) = m.get_one::("background") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("background".to_string(), value); + } + if let Some(v) = m.get_one::("max-tool-calls") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("max_tool_calls".to_string(), value); + } + if let Some(v) = m.get_one::("text") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("text".to_string(), value); + } + if let Some(vs) = m.get_many::("tools") { + body.insert("tools".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("prompt".to_string(), value); + } + if let Some(v) = m.get_one::("truncation") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("truncation".to_string(), value); + } + if let Some(v) = m.get_one::("input") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input".to_string(), value); + } + if let Some(v) = m.get_one::("include") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("include".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("parallel_tool_calls".to_string(), value); + } + if let Some(v) = m.get_one::("store") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("store".to_string(), value); + } + if let Some(v) = m.get_one::("instructions") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("instructions".to_string(), value); + } + if let Some(v) = m.get_one::("stream") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream".to_string(), value); + } + if let Some(v) = m.get_one::("stream-options") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("stream_options".to_string(), value); + } + if let Some(v) = m.get_one::("conversation") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("conversation".to_string(), value); + } + if let Some(v) = m.get_one::("context-management") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("context_management".to_string(), value); + } + if let Some(v) = m.get_one::("max-output-tokens") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("max_output_tokens".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/recorded.json new file mode 100644 index 000000000..bd75cb4cf --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__create/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "responses", + "create" + ], + "request": { + "method": "post", + "path": "/responses", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/input.json new file mode 100644 index 000000000..4eb155e12 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "responses", + "commands": [ + { + "name": "delete", + "description": "Deletes a model response with the given ID.\n", + "arguments": [ + { + "name": "response-id", + "required": true, + "description": "The ID of the response to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/responses/{response_id}", + "operationId": "deleteResponse", + "params": [ + { + "in": "path", + "name": "response_id", + "from": "argument:response-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/output.rs new file mode 100644 index 000000000..024db9b80 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("responses") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Deletes a model response with the given ID.\n") + .arg( + Arg::new("response-id") + .help("The ID of the response to delete.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["responses", "delete"] => handle_responses_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_responses_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let response_id = m.get_one::("response-id").map(String::as_str).unwrap_or(""); + let path = format!("/responses/{}", runtime::path_escape(response_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/recorded.json new file mode 100644 index 000000000..0a1e1f7b7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "responses", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/responses/{response_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/input.json new file mode 100644 index 000000000..641a1218f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/input.json @@ -0,0 +1,141 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "responses", + "commands": [ + { + "name": "input-items", + "commands": [ + { + "name": "list", + "description": "Returns a list of input items for a given response.", + "arguments": [ + { + "name": "response-id", + "required": true, + "description": "The ID of the response to retrieve input items for." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between\n1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "The order to return the input items in. Default is `desc`.\n- `asc`: Return the input items in ascending order.\n- `desc`: Return the input items in descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "An item ID to list items after, used in pagination.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "include", + "description": "Additional fields to include in the response. See the `include`\nparameter for Response creation above for more information.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "file_search_call.results", + "web_search_call.results", + "web_search_call.action.sources", + "message.input_image.image_url", + "computer_call_output.output.image_url", + "code_interpreter_call.outputs", + "reasoning.encrypted_content", + "message.output_text.logprobs" + ], + "arity": { + "minimum": 0 + } + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/responses/{response_id}/input_items", + "operationId": "listInputItems", + "params": [ + { + "in": "path", + "name": "response_id", + "from": "argument:response-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "include", + "from": "option:include" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/output.rs new file mode 100644 index 000000000..1ed46cc39 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/output.rs @@ -0,0 +1,92 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("responses") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("input-items") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of input items for a given response.") + .arg( + Arg::new("response-id") + .help("The ID of the response to retrieve input items for.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between\n1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("The order to return the input items in. Default is `desc`.\n- `asc`: Return the input items in ascending order.\n- `desc`: Return the input items in descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("An item ID to list items after, used in pagination.\n"), + ) + .arg( + Arg::new("include") + .long("include") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Additional fields to include in the response. See the `include`\nparameter for Response creation above for more information.\n"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["responses", "input-items", "list"] => handle_responses_input_items_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_responses_input_items_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let response_id = m.get_one::("response-id").map(String::as_str).unwrap_or(""); + let path = format!("/responses/{}/input_items", runtime::path_escape(response_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(vs) = m.get_many::("include") { + query.push(("include", vs.cloned().collect::>().join(","))); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/recorded.json new file mode 100644 index 000000000..ea2cc34b0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-items__list/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "responses", + "input-items", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/responses/{response_id}/input_items", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/input.json new file mode 100644 index 000000000..de8bd03d3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/input.json @@ -0,0 +1,226 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "responses", + "commands": [ + { + "name": "input-tokens", + "commands": [ + { + "name": "create", + "description": "Returns input token counts of the request.\n\nReturns an object with `object` set to `response.input_tokens` and an `input_tokens` count.", + "options": [ + { + "name": "model", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "input", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "previous-response-id", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tools", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "text", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "reasoning", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "truncation", + "group": "body", + "description": "The truncation strategy to use for the model response. - `auto`: If the input to this Response exceeds the model's context window size, the model will truncate the response to fit the context window by dropping items from the beginning of the conversation. - `disabled` (default): If the input size will exceed the context window size for a model, the request will fail with a 400 error.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "auto", + "disabled" + ] + } + ] + }, + { + "name": "instructions", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "conversation", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "tool-choice", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "parallel-tool-calls", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/responses/input_tokens", + "operationId": "Getinputtokencounts", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "input", + "from": "option:input", + "jsonPath": "input", + "encoding": "json" + }, + { + "name": "previous_response_id", + "from": "option:previous-response-id", + "jsonPath": "previous_response_id", + "encoding": "json" + }, + { + "name": "tools", + "from": "option:tools", + "jsonPath": "tools", + "encoding": "json" + }, + { + "name": "text", + "from": "option:text", + "jsonPath": "text", + "encoding": "json" + }, + { + "name": "reasoning", + "from": "option:reasoning", + "jsonPath": "reasoning", + "encoding": "json" + }, + { + "name": "truncation", + "from": "option:truncation", + "jsonPath": "truncation", + "encoding": "string" + }, + { + "name": "instructions", + "from": "option:instructions", + "jsonPath": "instructions", + "encoding": "json" + }, + { + "name": "conversation", + "from": "option:conversation", + "jsonPath": "conversation", + "encoding": "json" + }, + { + "name": "tool_choice", + "from": "option:tool-choice", + "jsonPath": "tool_choice", + "encoding": "json" + }, + { + "name": "parallel_tool_calls", + "from": "option:parallel-tool-calls", + "jsonPath": "parallel_tool_calls", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/output.rs new file mode 100644 index 000000000..16d29208d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/output.rs @@ -0,0 +1,153 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("responses") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("input-tokens") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Returns input token counts of the request.\n\nReturns an object with `object` set to `response.input_tokens` and an `input_tokens` count.") + .arg( + Arg::new("model") + .long("model"), + ) + .arg( + Arg::new("input") + .long("input"), + ) + .arg( + Arg::new("previous-response-id") + .long("previous-response-id"), + ) + .arg( + Arg::new("tools") + .long("tools"), + ) + .arg( + Arg::new("text") + .long("text"), + ) + .arg( + Arg::new("reasoning") + .long("reasoning"), + ) + .arg( + Arg::new("truncation") + .long("truncation") + .help("The truncation strategy to use for the model response. - `auto`: If the input to this Response exceeds the model's context window size, the model will truncate the response to fit the context window by dropping items from the beginning of the conversation. - `disabled` (default): If the input size will exceed the context window size for a model, the request will fail with a 400 error."), + ) + .arg( + Arg::new("instructions") + .long("instructions"), + ) + .arg( + Arg::new("conversation") + .long("conversation"), + ) + .arg( + Arg::new("tool-choice") + .long("tool-choice"), + ) + .arg( + Arg::new("parallel-tool-calls") + .long("parallel-tool-calls"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["responses", "input-tokens", "create"] => handle_responses_input_tokens_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_responses_input_tokens_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/responses/input_tokens".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("input") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input".to_string(), value); + } + if let Some(v) = m.get_one::("previous-response-id") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("previous_response_id".to_string(), value); + } + if let Some(v) = m.get_one::("tools") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tools".to_string(), value); + } + if let Some(v) = m.get_one::("text") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("text".to_string(), value); + } + if let Some(v) = m.get_one::("reasoning") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("reasoning".to_string(), value); + } + if let Some(v) = m.get_one::("truncation") { + body.insert("truncation".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("instructions") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("instructions".to_string(), value); + } + if let Some(v) = m.get_one::("conversation") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("conversation".to_string(), value); + } + if let Some(v) = m.get_one::("tool-choice") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("tool_choice".to_string(), value); + } + if let Some(v) = m.get_one::("parallel-tool-calls") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("parallel_tool_calls".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/recorded.json new file mode 100644 index 000000000..e5753b865 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__input-tokens__count/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "responses", + "input-tokens", + "create" + ], + "request": { + "method": "post", + "path": "/responses/input_tokens", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/input.json new file mode 100644 index 000000000..2d5bd618e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/input.json @@ -0,0 +1,125 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "responses", + "commands": [ + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieves a model response with the given ID.\n", + "arguments": [ + { + "name": "response-id", + "required": true, + "description": "The ID of the response to retrieve." + } + ], + "options": [ + { + "name": "include", + "description": "Additional fields to include in the response. See the `include`\nparameter for Response creation above for more information.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "file_search_call.results", + "web_search_call.results", + "web_search_call.action.sources", + "message.input_image.image_url", + "computer_call_output.output.image_url", + "code_interpreter_call.outputs", + "reasoning.encrypted_content", + "message.output_text.logprobs" + ], + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "stream", + "description": "If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section below](/docs/api-reference/responses-streaming)\nfor more information.\n", + "group": "query" + }, + { + "name": "starting-after", + "description": "The sequence number of the event after which to start streaming.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "include-obfuscation", + "description": "When true, stream obfuscation will be enabled. Stream obfuscation adds\nrandom characters to an `obfuscation` field on streaming delta events\nto normalize payload sizes as a mitigation to certain side-channel\nattacks. These obfuscation fields are included by default, but add a\nsmall amount of overhead to the data stream. You can set\n`include_obfuscation` to false to optimize for bandwidth if you trust\nthe network links between your application and the OpenAI API.\n", + "group": "query" + } + ], + "x-openapi": { + "method": "get", + "path": "/responses/{response_id}", + "operationId": "getResponse", + "params": [ + { + "in": "path", + "name": "response_id", + "from": "argument:response-id", + "required": true + }, + { + "in": "query", + "name": "include", + "from": "option:include" + }, + { + "in": "query", + "name": "stream", + "from": "option:stream" + }, + { + "in": "query", + "name": "starting_after", + "from": "option:starting-after" + }, + { + "in": "query", + "name": "include_obfuscation", + "from": "option:include-obfuscation" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/output.rs new file mode 100644 index 000000000..c6e452a61 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/output.rs @@ -0,0 +1,96 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("responses") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieves a model response with the given ID.\n") + .arg( + Arg::new("response-id") + .help("The ID of the response to retrieve.") + .required(true), + ) + .arg( + Arg::new("include") + .long("include") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("Additional fields to include in the response. See the `include`\nparameter for Response creation above for more information.\n"), + ) + .arg( + Arg::new("stream") + .long("stream") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section below](/docs/api-reference/responses-streaming)\nfor more information.\n"), + ) + .arg( + Arg::new("starting-after") + .long("starting-after") + .value_parser(clap::value_parser!(i64)) + .help("The sequence number of the event after which to start streaming.\n"), + ) + .arg( + Arg::new("include-obfuscation") + .long("include-obfuscation") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("When true, stream obfuscation will be enabled. Stream obfuscation adds\nrandom characters to an `obfuscation` field on streaming delta events\nto normalize payload sizes as a mitigation to certain side-channel\nattacks. These obfuscation fields are included by default, but add a\nsmall amount of overhead to the data stream. You can set\n`include_obfuscation` to false to optimize for bandwidth if you trust\nthe network links between your application and the OpenAI API.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["responses", "retrieve"] => handle_responses_retrieve(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_responses_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let response_id = m.get_one::("response-id").map(String::as_str).unwrap_or(""); + let path = format!("/responses/{}", runtime::path_escape(response_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(vs) = m.get_many::("include") { + query.push(("include", vs.cloned().collect::>().join(","))); + } + if let Some(v) = m.get_one::("stream") { + query.push(("stream", v.to_string())); + } + if let Some(v) = m.get_one::("starting-after") { + query.push(("starting_after", v.to_string())); + } + if let Some(v) = m.get_one::("include-obfuscation") { + query.push(("include_obfuscation", v.to_string())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/recorded.json new file mode 100644 index 000000000..a42b27712 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/responses__retrieve/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "responses", + "retrieve", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/responses/{response_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/input.json new file mode 100644 index 000000000..e7662d7d8 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/input.json @@ -0,0 +1,62 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "content", + "commands": [ + { + "name": "list", + "description": "Download a skill zip bundle by its ID.", + "arguments": [ + { + "name": "skill-id", + "required": true, + "description": "The identifier of the skill to download." + } + ], + "x-openapi": { + "method": "get", + "path": "/skills/{skill_id}/content", + "operationId": "GetSkillContent", + "params": [ + { + "in": "path", + "name": "skill_id", + "from": "argument:skill-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/output.rs new file mode 100644 index 000000000..2f737e8c6 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/output.rs @@ -0,0 +1,56 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("content") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Download a skill zip bundle by its ID.") + .arg( + Arg::new("skill-id") + .help("The identifier of the skill to download.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "content", "list"] => handle_skills_content_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_content_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let skill_id = m.get_one::("skill-id").map(String::as_str).unwrap_or(""); + let path = format!("/skills/{}/content", runtime::path_escape(skill_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/recorded.json new file mode 100644 index 000000000..f7bbd2159 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__content__retrieve/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "skills", + "content", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/skills/{skill_id}/content", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/input.json new file mode 100644 index 000000000..5114db155 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/input.json @@ -0,0 +1,68 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "create", + "description": "Create a new skill.", + "options": [ + { + "name": "files", + "group": "body", + "required": true, + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/skills", + "operationId": "CreateSkill", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "files", + "from": "option:files", + "jsonPath": "files", + "encoding": "json", + "required": true + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/output.rs new file mode 100644 index 000000000..93c265d62 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/output.rs @@ -0,0 +1,60 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new skill.") + .arg( + Arg::new("files") + .long("files") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "create"] => handle_skills_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/skills".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("files") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("files".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/recorded.json new file mode 100644 index 000000000..329712f82 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__create/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "skills", + "create", + "--files", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/skills", + "query": [], + "bodyFields": [ + "files" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/input.json new file mode 100644 index 000000000..bc3f7086c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "delete", + "description": "Delete a skill by its ID.", + "arguments": [ + { + "name": "skill-id", + "required": true, + "description": "The identifier of the skill to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/skills/{skill_id}", + "operationId": "DeleteSkill", + "params": [ + { + "in": "path", + "name": "skill_id", + "from": "argument:skill-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/output.rs new file mode 100644 index 000000000..e3fce036f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a skill by its ID.") + .arg( + Arg::new("skill-id") + .help("The identifier of the skill to delete.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "delete"] => handle_skills_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let skill_id = m.get_one::("skill-id").map(String::as_str).unwrap_or(""); + let path = format!("/skills/{}", runtime::path_escape(skill_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/recorded.json new file mode 100644 index 000000000..1a4e9050f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "skills", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/skills/{skill_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/input.json new file mode 100644 index 000000000..7a1049b5b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/input.json @@ -0,0 +1,95 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "list", + "description": "List all skills for the current project.", + "options": [ + { + "name": "limit", + "description": "Number of items to retrieve", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "Identifier for the last item from the previous pagination request", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/skills", + "operationId": "ListSkills", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/output.rs new file mode 100644 index 000000000..236e6aa2a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/output.rs @@ -0,0 +1,71 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List all skills for the current project.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of items to retrieve"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last item from the previous pagination request"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "list"] => handle_skills_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/skills".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/recorded.json new file mode 100644 index 000000000..6a9961958 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__list/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "skills", + "list" + ], + "request": { + "method": "get", + "path": "/skills", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/input.json new file mode 100644 index 000000000..7a1049b5b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/input.json @@ -0,0 +1,95 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "list", + "description": "List all skills for the current project.", + "options": [ + { + "name": "limit", + "description": "Number of items to retrieve", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "Identifier for the last item from the previous pagination request", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/skills", + "operationId": "ListSkills", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/output.rs new file mode 100644 index 000000000..236e6aa2a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/output.rs @@ -0,0 +1,71 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List all skills for the current project.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of items to retrieve"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last item from the previous pagination request"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "list"] => handle_skills_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/skills".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/recorded.json new file mode 100644 index 000000000..6a9961958 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__retrieve/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "skills", + "list" + ], + "request": { + "method": "get", + "path": "/skills", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/input.json new file mode 100644 index 000000000..5114db155 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/input.json @@ -0,0 +1,68 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "create", + "description": "Create a new skill.", + "options": [ + { + "name": "files", + "group": "body", + "required": true, + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/skills", + "operationId": "CreateSkill", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "files", + "from": "option:files", + "jsonPath": "files", + "encoding": "json", + "required": true + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/output.rs new file mode 100644 index 000000000..93c265d62 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/output.rs @@ -0,0 +1,60 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new skill.") + .arg( + Arg::new("files") + .long("files") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "create"] => handle_skills_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/skills".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("files") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("files".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/recorded.json new file mode 100644 index 000000000..329712f82 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__update/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "skills", + "create", + "--files", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/skills", + "query": [], + "bodyFields": [ + "files" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/input.json new file mode 100644 index 000000000..92f0628bb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/input.json @@ -0,0 +1,78 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "versions", + "commands": [ + { + "name": "content", + "commands": [ + { + "name": "list", + "description": "Download a skill version zip bundle.", + "arguments": [ + { + "name": "skill-id", + "required": true, + "description": "The identifier of the skill." + }, + { + "name": "version", + "required": true, + "description": "The skill version number." + } + ], + "x-openapi": { + "method": "get", + "path": "/skills/{skill_id}/versions/{version}/content", + "operationId": "GetSkillVersionContent", + "params": [ + { + "in": "path", + "name": "skill_id", + "from": "argument:skill-id", + "required": true + }, + { + "in": "path", + "name": "version", + "from": "argument:version", + "required": true + } + ] + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/output.rs new file mode 100644 index 000000000..46976fd58 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/output.rs @@ -0,0 +1,67 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("versions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("content") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Download a skill version zip bundle.") + .arg( + Arg::new("skill-id") + .help("The identifier of the skill.") + .required(true), + ) + .arg( + Arg::new("version") + .help("The skill version number.") + .required(true), + ), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "versions", "content", "list"] => handle_skills_versions_content_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_versions_content_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let skill_id = m.get_one::("skill-id").map(String::as_str).unwrap_or(""); + let version = m.get_one::("version").map(String::as_str).unwrap_or(""); + let path = format!("/skills/{}/versions/{}/content", runtime::path_escape(skill_id), runtime::path_escape(version)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/recorded.json new file mode 100644 index 000000000..ede851d66 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__content__retrieve/recorded.json @@ -0,0 +1,17 @@ +{ + "args": [ + "skills", + "versions", + "content", + "list", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/skills/{skill_id}/versions/{version}/content", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/input.json new file mode 100644 index 000000000..5deb63e22 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/input.json @@ -0,0 +1,99 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "versions", + "commands": [ + { + "name": "create", + "description": "Create a new immutable skill version.", + "arguments": [ + { + "name": "skill-id", + "required": true, + "description": "The identifier of the skill to version." + } + ], + "options": [ + { + "name": "files", + "group": "body", + "required": true, + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "default", + "group": "body", + "description": "Whether to set this version as the default." + } + ], + "x-openapi": { + "method": "post", + "path": "/skills/{skill_id}/versions", + "operationId": "CreateSkillVersion", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "skill_id", + "from": "argument:skill-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "files", + "from": "option:files", + "jsonPath": "files", + "encoding": "json", + "required": true + }, + { + "name": "default", + "from": "option:default", + "jsonPath": "default", + "encoding": "boolean" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/output.rs new file mode 100644 index 000000000..74d5c7745 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/output.rs @@ -0,0 +1,83 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("versions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new immutable skill version.") + .arg( + Arg::new("skill-id") + .help("The identifier of the skill to version.") + .required(true), + ) + .arg( + Arg::new("files") + .long("files") + .required(true), + ) + .arg( + Arg::new("default") + .long("default") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Whether to set this version as the default."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "versions", "create"] => handle_skills_versions_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_versions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let skill_id = m.get_one::("skill-id").map(String::as_str).unwrap_or(""); + let path = format!("/skills/{}/versions", runtime::path_escape(skill_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("files") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("files".to_string(), value); + } + if let Some(v) = m.get_one::("default") { + body.insert("default".to_string(), serde_json::Value::Bool(*v)); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/recorded.json new file mode 100644 index 000000000..9d222dfbc --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__create/recorded.json @@ -0,0 +1,20 @@ +{ + "args": [ + "skills", + "versions", + "create", + "EXAMPLE", + "--files", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/skills/{skill_id}/versions", + "query": [], + "bodyFields": [ + "files" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/input.json new file mode 100644 index 000000000..94d2944a1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/input.json @@ -0,0 +1,73 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "versions", + "commands": [ + { + "name": "delete", + "description": "Delete a skill version.", + "arguments": [ + { + "name": "skill-id", + "required": true, + "description": "The identifier of the skill." + }, + { + "name": "version", + "required": true, + "description": "The skill version number." + } + ], + "x-openapi": { + "method": "delete", + "path": "/skills/{skill_id}/versions/{version}", + "operationId": "DeleteSkillVersion", + "params": [ + { + "in": "path", + "name": "skill_id", + "from": "argument:skill-id", + "required": true + }, + { + "in": "path", + "name": "version", + "from": "argument:version", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/output.rs new file mode 100644 index 000000000..35db004c3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/output.rs @@ -0,0 +1,62 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("versions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a skill version.") + .arg( + Arg::new("skill-id") + .help("The identifier of the skill.") + .required(true), + ) + .arg( + Arg::new("version") + .help("The skill version number.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "versions", "delete"] => handle_skills_versions_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_versions_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let skill_id = m.get_one::("skill-id").map(String::as_str).unwrap_or(""); + let version = m.get_one::("version").map(String::as_str).unwrap_or(""); + let path = format!("/skills/{}/versions/{}", runtime::path_escape(skill_id), runtime::path_escape(version)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/recorded.json new file mode 100644 index 000000000..284c347f2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__delete/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "skills", + "versions", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/skills/{skill_id}/versions/{version}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/input.json new file mode 100644 index 000000000..d51038922 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/input.json @@ -0,0 +1,113 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "versions", + "commands": [ + { + "name": "list", + "description": "List skill versions for a skill.", + "arguments": [ + { + "name": "skill-id", + "required": true, + "description": "The identifier of the skill." + } + ], + "options": [ + { + "name": "limit", + "description": "Number of versions to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order of results by version number.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "The skill version ID to start after.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/skills/{skill_id}/versions", + "operationId": "ListSkillVersions", + "params": [ + { + "in": "path", + "name": "skill_id", + "from": "argument:skill-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/output.rs new file mode 100644 index 000000000..3f46c6839 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/output.rs @@ -0,0 +1,82 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("versions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List skill versions for a skill.") + .arg( + Arg::new("skill-id") + .help("The identifier of the skill.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of versions to retrieve."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order of results by version number."), + ) + .arg( + Arg::new("after") + .long("after") + .help("The skill version ID to start after."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "versions", "list"] => handle_skills_versions_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_versions_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let skill_id = m.get_one::("skill-id").map(String::as_str).unwrap_or(""); + let path = format!("/skills/{}/versions", runtime::path_escape(skill_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/recorded.json new file mode 100644 index 000000000..a2ce735e9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__list/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "skills", + "versions", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/skills/{skill_id}/versions", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/input.json new file mode 100644 index 000000000..d51038922 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/input.json @@ -0,0 +1,113 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "skills", + "commands": [ + { + "name": "versions", + "commands": [ + { + "name": "list", + "description": "List skill versions for a skill.", + "arguments": [ + { + "name": "skill-id", + "required": true, + "description": "The identifier of the skill." + } + ], + "options": [ + { + "name": "limit", + "description": "Number of versions to retrieve.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order of results by version number.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "The skill version ID to start after.", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/skills/{skill_id}/versions", + "operationId": "ListSkillVersions", + "params": [ + { + "in": "path", + "name": "skill_id", + "from": "argument:skill-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/output.rs new file mode 100644 index 000000000..3f46c6839 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/output.rs @@ -0,0 +1,82 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("skills") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("versions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List skill versions for a skill.") + .arg( + Arg::new("skill-id") + .help("The identifier of the skill.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of versions to retrieve."), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order of results by version number."), + ) + .arg( + Arg::new("after") + .long("after") + .help("The skill version ID to start after."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["skills", "versions", "list"] => handle_skills_versions_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_skills_versions_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let skill_id = m.get_one::("skill-id").map(String::as_str).unwrap_or(""); + let path = format!("/skills/{}/versions", runtime::path_escape(skill_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/recorded.json new file mode 100644 index 000000000..a2ce735e9 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/skills__versions__retrieve/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "skills", + "versions", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/skills/{skill_id}/versions", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/input.json new file mode 100644 index 000000000..6c9489116 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/input.json @@ -0,0 +1,145 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "uploads", + "commands": [ + { + "name": "create", + "description": "Creates an intermediate [Upload](/docs/api-reference/uploads/object) object\nthat you can add [Parts](/docs/api-reference/uploads/part-object) to.\nCurrently, an Upload can accept at most 8 GB in total and expires after an\nhour after you create it.\n\nOnce you complete the Upload, we will create a\n[File](/docs/api-reference/files/object) object that contains all the parts\nyou uploaded. This File is usable in the rest of our platform as a regular\nFile object.\n\nFor certain `purpose` values, the correct `mime_type` must be specified. \nPlease refer to documentation for the \n[supported MIME types for your use case](/docs/assistants/tools/file-search#supported-files).\n\nFor guidance on the proper filename extensions for each purpose, please\nfollow the documentation on [creating a\nFile](/docs/api-reference/files/create).\n\nReturns the Upload object with status `pending`.\n", + "options": [ + { + "name": "filename", + "group": "body", + "required": true, + "description": "The name of the file to upload.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "purpose", + "group": "body", + "required": true, + "description": "The intended purpose of the uploaded file.\n\nSee the [documentation on File\npurposes](/docs/api-reference/files/create#files-create-purpose).\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "assistants", + "batch", + "fine-tune", + "vision" + ] + } + ] + }, + { + "name": "bytes", + "group": "body", + "required": true, + "description": "The number of bytes in the file you are uploading.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "mime-type", + "group": "body", + "required": true, + "description": "The MIME type of the file.\n\n\nThis must fall within the supported MIME types for your file purpose. See\nthe supported MIME types for assistants and vision.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted.", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/uploads", + "operationId": "createUpload", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "filename", + "from": "option:filename", + "jsonPath": "filename", + "encoding": "string", + "required": true + }, + { + "name": "purpose", + "from": "option:purpose", + "jsonPath": "purpose", + "encoding": "string", + "required": true + }, + { + "name": "bytes", + "from": "option:bytes", + "jsonPath": "bytes", + "encoding": "integer", + "required": true + }, + { + "name": "mime_type", + "from": "option:mime-type", + "jsonPath": "mime_type", + "encoding": "string", + "required": true + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/output.rs new file mode 100644 index 000000000..e1fd35b4c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/output.rs @@ -0,0 +1,97 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("uploads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates an intermediate [Upload](/docs/api-reference/uploads/object) object\nthat you can add [Parts](/docs/api-reference/uploads/part-object) to.\nCurrently, an Upload can accept at most 8 GB in total and expires after an\nhour after you create it.\n\nOnce you complete the Upload, we will create a\n[File](/docs/api-reference/files/object) object that contains all the parts\nyou uploaded. This File is usable in the rest of our platform as a regular\nFile object.\n\nFor certain `purpose` values, the correct `mime_type` must be specified. \nPlease refer to documentation for the \n[supported MIME types for your use case](/docs/assistants/tools/file-search#supported-files).\n\nFor guidance on the proper filename extensions for each purpose, please\nfollow the documentation on [creating a\nFile](/docs/api-reference/files/create).\n\nReturns the Upload object with status `pending`.\n") + .arg( + Arg::new("filename") + .long("filename") + .help("The name of the file to upload.\n") + .required(true), + ) + .arg( + Arg::new("purpose") + .long("purpose") + .help("The intended purpose of the uploaded file.\n\nSee the [documentation on File\npurposes](/docs/api-reference/files/create#files-create-purpose).\n") + .required(true), + ) + .arg( + Arg::new("bytes") + .long("bytes") + .value_parser(clap::value_parser!(i64)) + .help("The number of bytes in the file you are uploading.\n") + .required(true), + ) + .arg( + Arg::new("mime-type") + .long("mime-type") + .help("The MIME type of the file.\n\n\nThis must fall within the supported MIME types for your file purpose. See\nthe supported MIME types for assistants and vision.\n") + .required(true), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["uploads", "create"] => handle_uploads_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_uploads_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/uploads".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("filename") { + body.insert("filename".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("purpose") { + body.insert("purpose".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("bytes") { + body.insert("bytes".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("mime-type") { + body.insert("mime_type".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/recorded.json new file mode 100644 index 000000000..ebf4eb3f3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__cancel/recorded.json @@ -0,0 +1,27 @@ +{ + "args": [ + "uploads", + "create", + "--filename", + "EXAMPLE", + "--purpose", + "EXAMPLE", + "--bytes", + "1", + "--mime-type", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/uploads", + "query": [], + "bodyFields": [ + "bytes", + "filename", + "mime_type", + "purpose" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/input.json new file mode 100644 index 000000000..6c9489116 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/input.json @@ -0,0 +1,145 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "uploads", + "commands": [ + { + "name": "create", + "description": "Creates an intermediate [Upload](/docs/api-reference/uploads/object) object\nthat you can add [Parts](/docs/api-reference/uploads/part-object) to.\nCurrently, an Upload can accept at most 8 GB in total and expires after an\nhour after you create it.\n\nOnce you complete the Upload, we will create a\n[File](/docs/api-reference/files/object) object that contains all the parts\nyou uploaded. This File is usable in the rest of our platform as a regular\nFile object.\n\nFor certain `purpose` values, the correct `mime_type` must be specified. \nPlease refer to documentation for the \n[supported MIME types for your use case](/docs/assistants/tools/file-search#supported-files).\n\nFor guidance on the proper filename extensions for each purpose, please\nfollow the documentation on [creating a\nFile](/docs/api-reference/files/create).\n\nReturns the Upload object with status `pending`.\n", + "options": [ + { + "name": "filename", + "group": "body", + "required": true, + "description": "The name of the file to upload.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "purpose", + "group": "body", + "required": true, + "description": "The intended purpose of the uploaded file.\n\nSee the [documentation on File\npurposes](/docs/api-reference/files/create#files-create-purpose).\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "assistants", + "batch", + "fine-tune", + "vision" + ] + } + ] + }, + { + "name": "bytes", + "group": "body", + "required": true, + "description": "The number of bytes in the file you are uploading.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "mime-type", + "group": "body", + "required": true, + "description": "The MIME type of the file.\n\n\nThis must fall within the supported MIME types for your file purpose. See\nthe supported MIME types for assistants and vision.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted.", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/uploads", + "operationId": "createUpload", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "filename", + "from": "option:filename", + "jsonPath": "filename", + "encoding": "string", + "required": true + }, + { + "name": "purpose", + "from": "option:purpose", + "jsonPath": "purpose", + "encoding": "string", + "required": true + }, + { + "name": "bytes", + "from": "option:bytes", + "jsonPath": "bytes", + "encoding": "integer", + "required": true + }, + { + "name": "mime_type", + "from": "option:mime-type", + "jsonPath": "mime_type", + "encoding": "string", + "required": true + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/output.rs new file mode 100644 index 000000000..e1fd35b4c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/output.rs @@ -0,0 +1,97 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("uploads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates an intermediate [Upload](/docs/api-reference/uploads/object) object\nthat you can add [Parts](/docs/api-reference/uploads/part-object) to.\nCurrently, an Upload can accept at most 8 GB in total and expires after an\nhour after you create it.\n\nOnce you complete the Upload, we will create a\n[File](/docs/api-reference/files/object) object that contains all the parts\nyou uploaded. This File is usable in the rest of our platform as a regular\nFile object.\n\nFor certain `purpose` values, the correct `mime_type` must be specified. \nPlease refer to documentation for the \n[supported MIME types for your use case](/docs/assistants/tools/file-search#supported-files).\n\nFor guidance on the proper filename extensions for each purpose, please\nfollow the documentation on [creating a\nFile](/docs/api-reference/files/create).\n\nReturns the Upload object with status `pending`.\n") + .arg( + Arg::new("filename") + .long("filename") + .help("The name of the file to upload.\n") + .required(true), + ) + .arg( + Arg::new("purpose") + .long("purpose") + .help("The intended purpose of the uploaded file.\n\nSee the [documentation on File\npurposes](/docs/api-reference/files/create#files-create-purpose).\n") + .required(true), + ) + .arg( + Arg::new("bytes") + .long("bytes") + .value_parser(clap::value_parser!(i64)) + .help("The number of bytes in the file you are uploading.\n") + .required(true), + ) + .arg( + Arg::new("mime-type") + .long("mime-type") + .help("The MIME type of the file.\n\n\nThis must fall within the supported MIME types for your file purpose. See\nthe supported MIME types for assistants and vision.\n") + .required(true), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["uploads", "create"] => handle_uploads_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_uploads_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/uploads".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("filename") { + body.insert("filename".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("purpose") { + body.insert("purpose".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("bytes") { + body.insert("bytes".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("mime-type") { + body.insert("mime_type".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/recorded.json new file mode 100644 index 000000000..ebf4eb3f3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__complete/recorded.json @@ -0,0 +1,27 @@ +{ + "args": [ + "uploads", + "create", + "--filename", + "EXAMPLE", + "--purpose", + "EXAMPLE", + "--bytes", + "1", + "--mime-type", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/uploads", + "query": [], + "bodyFields": [ + "bytes", + "filename", + "mime_type", + "purpose" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/input.json new file mode 100644 index 000000000..6c9489116 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/input.json @@ -0,0 +1,145 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "uploads", + "commands": [ + { + "name": "create", + "description": "Creates an intermediate [Upload](/docs/api-reference/uploads/object) object\nthat you can add [Parts](/docs/api-reference/uploads/part-object) to.\nCurrently, an Upload can accept at most 8 GB in total and expires after an\nhour after you create it.\n\nOnce you complete the Upload, we will create a\n[File](/docs/api-reference/files/object) object that contains all the parts\nyou uploaded. This File is usable in the rest of our platform as a regular\nFile object.\n\nFor certain `purpose` values, the correct `mime_type` must be specified. \nPlease refer to documentation for the \n[supported MIME types for your use case](/docs/assistants/tools/file-search#supported-files).\n\nFor guidance on the proper filename extensions for each purpose, please\nfollow the documentation on [creating a\nFile](/docs/api-reference/files/create).\n\nReturns the Upload object with status `pending`.\n", + "options": [ + { + "name": "filename", + "group": "body", + "required": true, + "description": "The name of the file to upload.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "purpose", + "group": "body", + "required": true, + "description": "The intended purpose of the uploaded file.\n\nSee the [documentation on File\npurposes](/docs/api-reference/files/create#files-create-purpose).\n", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "assistants", + "batch", + "fine-tune", + "vision" + ] + } + ] + }, + { + "name": "bytes", + "group": "body", + "required": true, + "description": "The number of bytes in the file you are uploading.\n", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "mime-type", + "group": "body", + "required": true, + "description": "The MIME type of the file.\n\n\nThis must fall within the supported MIME types for your file purpose. See\nthe supported MIME types for assistants and vision.\n", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted.", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/uploads", + "operationId": "createUpload", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "filename", + "from": "option:filename", + "jsonPath": "filename", + "encoding": "string", + "required": true + }, + { + "name": "purpose", + "from": "option:purpose", + "jsonPath": "purpose", + "encoding": "string", + "required": true + }, + { + "name": "bytes", + "from": "option:bytes", + "jsonPath": "bytes", + "encoding": "integer", + "required": true + }, + { + "name": "mime_type", + "from": "option:mime-type", + "jsonPath": "mime_type", + "encoding": "string", + "required": true + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/output.rs new file mode 100644 index 000000000..e1fd35b4c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/output.rs @@ -0,0 +1,97 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("uploads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates an intermediate [Upload](/docs/api-reference/uploads/object) object\nthat you can add [Parts](/docs/api-reference/uploads/part-object) to.\nCurrently, an Upload can accept at most 8 GB in total and expires after an\nhour after you create it.\n\nOnce you complete the Upload, we will create a\n[File](/docs/api-reference/files/object) object that contains all the parts\nyou uploaded. This File is usable in the rest of our platform as a regular\nFile object.\n\nFor certain `purpose` values, the correct `mime_type` must be specified. \nPlease refer to documentation for the \n[supported MIME types for your use case](/docs/assistants/tools/file-search#supported-files).\n\nFor guidance on the proper filename extensions for each purpose, please\nfollow the documentation on [creating a\nFile](/docs/api-reference/files/create).\n\nReturns the Upload object with status `pending`.\n") + .arg( + Arg::new("filename") + .long("filename") + .help("The name of the file to upload.\n") + .required(true), + ) + .arg( + Arg::new("purpose") + .long("purpose") + .help("The intended purpose of the uploaded file.\n\nSee the [documentation on File\npurposes](/docs/api-reference/files/create#files-create-purpose).\n") + .required(true), + ) + .arg( + Arg::new("bytes") + .long("bytes") + .value_parser(clap::value_parser!(i64)) + .help("The number of bytes in the file you are uploading.\n") + .required(true), + ) + .arg( + Arg::new("mime-type") + .long("mime-type") + .help("The MIME type of the file.\n\n\nThis must fall within the supported MIME types for your file purpose. See\nthe supported MIME types for assistants and vision.\n") + .required(true), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["uploads", "create"] => handle_uploads_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_uploads_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/uploads".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("filename") { + body.insert("filename".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("purpose") { + body.insert("purpose".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("bytes") { + body.insert("bytes".to_string(), serde_json::Value::from(*v)); + } + if let Some(v) = m.get_one::("mime-type") { + body.insert("mime_type".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/recorded.json new file mode 100644 index 000000000..ebf4eb3f3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__create/recorded.json @@ -0,0 +1,27 @@ +{ + "args": [ + "uploads", + "create", + "--filename", + "EXAMPLE", + "--purpose", + "EXAMPLE", + "--bytes", + "1", + "--mime-type", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/uploads", + "query": [], + "bodyFields": [ + "bytes", + "filename", + "mime_type", + "purpose" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/input.json new file mode 100644 index 000000000..6e76ac0cd --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/input.json @@ -0,0 +1,89 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "uploads", + "commands": [ + { + "name": "parts", + "commands": [ + { + "name": "create", + "description": "Adds a [Part](/docs/api-reference/uploads/part-object) to an [Upload](/docs/api-reference/uploads/object) object. A Part represents a chunk of bytes from the file you are trying to upload. \n\nEach Part can be at most 64 MB, and you can add Parts until you hit the Upload maximum of 8 GB.\n\nIt is possible to add multiple Parts in parallel. You can decide the intended order of the Parts when you [complete the Upload](/docs/api-reference/uploads/complete).\n", + "arguments": [ + { + "name": "upload-id", + "required": true, + "description": "The ID of the Upload.\n" + } + ], + "options": [ + { + "name": "data", + "group": "body", + "required": true, + "description": "The chunk of bytes for this Part.\n", + "arguments": [ + { + "name": "path" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/uploads/{upload_id}/parts", + "operationId": "addUploadPart", + "contentType": "multipart/form-data", + "params": [ + { + "in": "path", + "name": "upload_id", + "from": "argument:upload-id", + "required": true + } + ], + "body": { + "style": "multipart", + "contentType": "multipart/form-data", + "properties": [ + { + "name": "data", + "from": "option:data", + "jsonPath": "data", + "encoding": "file", + "required": true + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/output.rs new file mode 100644 index 000000000..16d3bf2e0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/output.rs @@ -0,0 +1,70 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("uploads") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("parts") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Adds a [Part](/docs/api-reference/uploads/part-object) to an [Upload](/docs/api-reference/uploads/object) object. A Part represents a chunk of bytes from the file you are trying to upload. \n\nEach Part can be at most 64 MB, and you can add Parts until you hit the Upload maximum of 8 GB.\n\nIt is possible to add multiple Parts in parallel. You can decide the intended order of the Parts when you [complete the Upload](/docs/api-reference/uploads/complete).\n") + .arg( + Arg::new("upload-id") + .help("The ID of the Upload.\n") + .required(true), + ) + .arg( + Arg::new("data") + .long("data") + .help("The chunk of bytes for this Part.\n") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["uploads", "parts", "create"] => handle_uploads_parts_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_uploads_parts_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let upload_id = m.get_one::("upload-id").map(String::as_str).unwrap_or(""); + let path = format!("/uploads/{}/parts", runtime::path_escape(upload_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("data") { + body.insert("data".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/recorded.json new file mode 100644 index 000000000..6e89d07f7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/uploads__parts__create/recorded.json @@ -0,0 +1,20 @@ +{ + "args": [ + "uploads", + "parts", + "create", + "EXAMPLE", + "--data", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/uploads/{upload_id}/parts", + "query": [], + "bodyFields": [ + "data" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/input.json new file mode 100644 index 000000000..7534c5ecd --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/input.json @@ -0,0 +1,149 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "create", + "description": "Create a vector store.", + "options": [ + { + "name": "file-ids", + "group": "body", + "description": "A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files.", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "name", + "group": "body", + "description": "The name of the vector store.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "description", + "group": "body", + "description": "A description for the vector store. Can be used to describe the vector store's purpose.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "The expiration policy for a vector store.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "chunking-strategy", + "group": "body", + "description": "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/vector_stores", + "operationId": "createVectorStore", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "file_ids", + "from": "option:file-ids", + "jsonPath": "file_ids", + "encoding": "array" + }, + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string" + }, + { + "name": "description", + "from": "option:description", + "jsonPath": "description", + "encoding": "string" + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + }, + { + "name": "chunking_strategy", + "from": "option:chunking-strategy", + "jsonPath": "chunking_strategy", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/output.rs new file mode 100644 index 000000000..ba40c914b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/output.rs @@ -0,0 +1,105 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a vector store.") + .arg( + Arg::new("file-ids") + .long("file-ids") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files."), + ) + .arg( + Arg::new("name") + .long("name") + .help("The name of the vector store."), + ) + .arg( + Arg::new("description") + .long("description") + .help("A description for the vector store. Can be used to describe the vector store's purpose."), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("The expiration policy for a vector store."), + ) + .arg( + Arg::new("chunking-strategy") + .long("chunking-strategy") + .help("The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty."), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "create"] => handle_vector_stores_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/vector_stores".to_string(); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("file-ids") { + body.insert("file_ids".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("description") { + body.insert("description".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + if let Some(v) = m.get_one::("chunking-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chunking_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/recorded.json new file mode 100644 index 000000000..b7112a01e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__create/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "vector-stores", + "create" + ], + "request": { + "method": "post", + "path": "/vector_stores", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/input.json new file mode 100644 index 000000000..12a9ab282 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "delete", + "description": "Delete a vector store.", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/vector_stores/{vector_store_id}", + "operationId": "deleteVectorStore", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/output.rs new file mode 100644 index 000000000..b18fe206e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a vector store.") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store to delete.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "delete"] => handle_vector_stores_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}", runtime::path_escape(vector_store_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/recorded.json new file mode 100644 index 000000000..b3613011f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "vector-stores", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/vector_stores/{vector_store_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/input.json new file mode 100644 index 000000000..3d42cd284 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/input.json @@ -0,0 +1,140 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "file-batches", + "commands": [ + { + "name": "create", + "description": "Create a vector store file batch.", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store for which to create a File Batch.\n" + } + ], + "options": [ + { + "name": "file-ids", + "group": "body", + "description": "A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files. If `attributes` or `chunking_strategy` are provided, they will be applied to all files in the batch. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `files`.", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "files", + "group": "body", + "description": "A list of objects that each include a `file_id` plus optional `attributes` or `chunking_strategy`. Use this when you need to override metadata for specific files. The global `attributes` or `chunking_strategy` will be ignored and must be specified for each file. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `file_ids`.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "chunking-strategy", + "group": "body", + "description": "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "attributes", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/vector_stores/{vector_store_id}/file_batches", + "operationId": "createVectorStoreFileBatch", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "file_ids", + "from": "option:file-ids", + "jsonPath": "file_ids", + "encoding": "array" + }, + { + "name": "files", + "from": "option:files", + "jsonPath": "files", + "encoding": "array" + }, + { + "name": "chunking_strategy", + "from": "option:chunking-strategy", + "jsonPath": "chunking_strategy", + "encoding": "json" + }, + { + "name": "attributes", + "from": "option:attributes", + "jsonPath": "attributes", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/output.rs new file mode 100644 index 000000000..f002e1621 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/output.rs @@ -0,0 +1,100 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("file-batches") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a vector store file batch.") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store for which to create a File Batch.\n") + .required(true), + ) + .arg( + Arg::new("file-ids") + .long("file-ids") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files. If `attributes` or `chunking_strategy` are provided, they will be applied to all files in the batch. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `files`."), + ) + .arg( + Arg::new("files") + .long("files") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of objects that each include a `file_id` plus optional `attributes` or `chunking_strategy`. Use this when you need to override metadata for specific files. The global `attributes` or `chunking_strategy` will be ignored and must be specified for each file. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `file_ids`."), + ) + .arg( + Arg::new("chunking-strategy") + .long("chunking-strategy") + .help("The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy."), + ) + .arg( + Arg::new("attributes") + .long("attributes"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "file-batches", "create"] => handle_vector_stores_file_batches_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_file_batches_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/file_batches", runtime::path_escape(vector_store_id)); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("file-ids") { + body.insert("file_ids".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(vs) = m.get_many::("files") { + body.insert("files".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("chunking-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chunking_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("attributes") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("attributes".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/recorded.json new file mode 100644 index 000000000..78efe32e1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__cancel/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "vector-stores", + "file-batches", + "create", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/vector_stores/{vector_store_id}/file_batches", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/input.json new file mode 100644 index 000000000..3d42cd284 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/input.json @@ -0,0 +1,140 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "file-batches", + "commands": [ + { + "name": "create", + "description": "Create a vector store file batch.", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store for which to create a File Batch.\n" + } + ], + "options": [ + { + "name": "file-ids", + "group": "body", + "description": "A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files. If `attributes` or `chunking_strategy` are provided, they will be applied to all files in the batch. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `files`.", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "files", + "group": "body", + "description": "A list of objects that each include a `file_id` plus optional `attributes` or `chunking_strategy`. Use this when you need to override metadata for specific files. The global `attributes` or `chunking_strategy` will be ignored and must be specified for each file. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `file_ids`.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "chunking-strategy", + "group": "body", + "description": "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "attributes", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/vector_stores/{vector_store_id}/file_batches", + "operationId": "createVectorStoreFileBatch", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "file_ids", + "from": "option:file-ids", + "jsonPath": "file_ids", + "encoding": "array" + }, + { + "name": "files", + "from": "option:files", + "jsonPath": "files", + "encoding": "array" + }, + { + "name": "chunking_strategy", + "from": "option:chunking-strategy", + "jsonPath": "chunking_strategy", + "encoding": "json" + }, + { + "name": "attributes", + "from": "option:attributes", + "jsonPath": "attributes", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/output.rs new file mode 100644 index 000000000..f002e1621 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/output.rs @@ -0,0 +1,100 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("file-batches") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a vector store file batch.") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store for which to create a File Batch.\n") + .required(true), + ) + .arg( + Arg::new("file-ids") + .long("file-ids") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files. If `attributes` or `chunking_strategy` are provided, they will be applied to all files in the batch. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `files`."), + ) + .arg( + Arg::new("files") + .long("files") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of objects that each include a `file_id` plus optional `attributes` or `chunking_strategy`. Use this when you need to override metadata for specific files. The global `attributes` or `chunking_strategy` will be ignored and must be specified for each file. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `file_ids`."), + ) + .arg( + Arg::new("chunking-strategy") + .long("chunking-strategy") + .help("The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy."), + ) + .arg( + Arg::new("attributes") + .long("attributes"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "file-batches", "create"] => handle_vector_stores_file_batches_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_file_batches_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/file_batches", runtime::path_escape(vector_store_id)); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("file-ids") { + body.insert("file_ids".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(vs) = m.get_many::("files") { + body.insert("files".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("chunking-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chunking_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("attributes") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("attributes".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/recorded.json new file mode 100644 index 000000000..78efe32e1 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__create/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "vector-stores", + "file-batches", + "create", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/vector_stores/{vector_store_id}/file_batches", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/input.json new file mode 100644 index 000000000..c6865caa2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/input.json @@ -0,0 +1,76 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "file-batches", + "commands": [ + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieves a vector store file batch.", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store that the file batch belongs to." + }, + { + "name": "batch-id", + "required": true, + "description": "The ID of the file batch being retrieved." + } + ], + "x-openapi": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/file_batches/{batch_id}", + "operationId": "getVectorStoreFileBatch", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + }, + { + "in": "path", + "name": "batch_id", + "from": "argument:batch-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/output.rs new file mode 100644 index 000000000..20a08a97f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/output.rs @@ -0,0 +1,63 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("file-batches") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieves a vector store file batch.") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store that the file batch belongs to.") + .required(true), + ) + .arg( + Arg::new("batch-id") + .help("The ID of the file batch being retrieved.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "file-batches", "retrieve"] => handle_vector_stores_file_batches_retrieve(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_file_batches_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let batch_id = m.get_one::("batch-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/file_batches/{}", runtime::path_escape(vector_store_id), runtime::path_escape(batch_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/recorded.json new file mode 100644 index 000000000..3ade4a311 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__list-files/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "vector-stores", + "file-batches", + "retrieve", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/file_batches/{batch_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/input.json new file mode 100644 index 000000000..c6865caa2 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/input.json @@ -0,0 +1,76 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "file-batches", + "commands": [ + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieves a vector store file batch.", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store that the file batch belongs to." + }, + { + "name": "batch-id", + "required": true, + "description": "The ID of the file batch being retrieved." + } + ], + "x-openapi": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/file_batches/{batch_id}", + "operationId": "getVectorStoreFileBatch", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + }, + { + "in": "path", + "name": "batch_id", + "from": "argument:batch-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/output.rs new file mode 100644 index 000000000..20a08a97f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/output.rs @@ -0,0 +1,63 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("file-batches") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieves a vector store file batch.") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store that the file batch belongs to.") + .required(true), + ) + .arg( + Arg::new("batch-id") + .help("The ID of the file batch being retrieved.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "file-batches", "retrieve"] => handle_vector_stores_file_batches_retrieve(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_file_batches_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let batch_id = m.get_one::("batch-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/file_batches/{}", runtime::path_escape(vector_store_id), runtime::path_escape(batch_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/recorded.json new file mode 100644 index 000000000..3ade4a311 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__file-batches__retrieve/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "vector-stores", + "file-batches", + "retrieve", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/file_batches/{batch_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/input.json new file mode 100644 index 000000000..1cab8f87c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/input.json @@ -0,0 +1,149 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "list", + "description": "Returns a list of vector store files.", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store that the files belong to." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "filter", + "description": "Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "in_progress", + "completed", + "failed", + "cancelled" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/files", + "operationId": "listVectorStoreFiles", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + }, + { + "in": "query", + "name": "filter", + "from": "option:filter" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/output.rs new file mode 100644 index 000000000..676d2a16c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/output.rs @@ -0,0 +1,98 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of vector store files.") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store that the files belong to.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ) + .arg( + Arg::new("filter") + .long("filter") + .help("Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "files", "list"] => handle_vector_stores_files_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_files_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/files", runtime::path_escape(vector_store_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + if let Some(v) = m.get_one::("filter") { + query.push(("filter", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/recorded.json new file mode 100644 index 000000000..666956e0c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__content/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "vector-stores", + "files", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/files", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/input.json new file mode 100644 index 000000000..bf660480b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/input.json @@ -0,0 +1,120 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "create", + "description": "Create a vector store file by attaching a [File](/docs/api-reference/files) to a [vector store](/docs/api-reference/vector-stores/object).", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store for which to create a File.\n" + } + ], + "options": [ + { + "name": "file-id", + "group": "body", + "required": true, + "description": "A [File](/docs/api-reference/files) ID that the vector store should use. Useful for tools like `file_search` that can access files. For multi-file ingestion, we recommend [`file_batches`](/docs/api-reference/vector-stores-file-batches/createBatch) to minimize per-vector-store write requests.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "chunking-strategy", + "group": "body", + "description": "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "attributes", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/vector_stores/{vector_store_id}/files", + "operationId": "createVectorStoreFile", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "file_id", + "from": "option:file-id", + "jsonPath": "file_id", + "encoding": "string", + "required": true + }, + { + "name": "chunking_strategy", + "from": "option:chunking-strategy", + "jsonPath": "chunking_strategy", + "encoding": "json" + }, + { + "name": "attributes", + "from": "option:attributes", + "jsonPath": "attributes", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/output.rs new file mode 100644 index 000000000..05ce38b61 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/output.rs @@ -0,0 +1,89 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a vector store file by attaching a [File](/docs/api-reference/files) to a [vector store](/docs/api-reference/vector-stores/object).") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store for which to create a File.\n") + .required(true), + ) + .arg( + Arg::new("file-id") + .long("file-id") + .help("A [File](/docs/api-reference/files) ID that the vector store should use. Useful for tools like `file_search` that can access files. For multi-file ingestion, we recommend [`file_batches`](/docs/api-reference/vector-stores-file-batches/createBatch) to minimize per-vector-store write requests.") + .required(true), + ) + .arg( + Arg::new("chunking-strategy") + .long("chunking-strategy") + .help("The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy."), + ) + .arg( + Arg::new("attributes") + .long("attributes"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "files", "create"] => handle_vector_stores_files_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_files_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/files", runtime::path_escape(vector_store_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("file-id") { + body.insert("file_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("chunking-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chunking_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("attributes") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("attributes".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/recorded.json new file mode 100644 index 000000000..21c4331f6 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__create/recorded.json @@ -0,0 +1,20 @@ +{ + "args": [ + "vector-stores", + "files", + "create", + "EXAMPLE", + "--file-id", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/vector_stores/{vector_store_id}/files", + "query": [], + "bodyFields": [ + "file_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/input.json new file mode 100644 index 000000000..528e0b157 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/input.json @@ -0,0 +1,73 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "delete", + "description": "Delete a vector store file. This will remove the file from the vector store but the file itself will not be deleted. To delete the file, use the [delete file](/docs/api-reference/files/delete) endpoint.", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store that the file belongs to." + }, + { + "name": "file-id", + "required": true, + "description": "The ID of the file to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/vector_stores/{vector_store_id}/files/{file_id}", + "operationId": "deleteVectorStoreFile", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + }, + { + "in": "path", + "name": "file_id", + "from": "argument:file-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/output.rs new file mode 100644 index 000000000..c7b560c73 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/output.rs @@ -0,0 +1,62 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Delete a vector store file. This will remove the file from the vector store but the file itself will not be deleted. To delete the file, use the [delete file](/docs/api-reference/files/delete) endpoint.") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store that the file belongs to.") + .required(true), + ) + .arg( + Arg::new("file-id") + .help("The ID of the file to delete.") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "files", "delete"] => handle_vector_stores_files_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_files_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let file_id = m.get_one::("file-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/files/{}", runtime::path_escape(vector_store_id), runtime::path_escape(file_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/recorded.json new file mode 100644 index 000000000..36e7452df --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__delete/recorded.json @@ -0,0 +1,16 @@ +{ + "args": [ + "vector-stores", + "files", + "delete", + "EXAMPLE", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/vector_stores/{vector_store_id}/files/{file_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/input.json new file mode 100644 index 000000000..1cab8f87c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/input.json @@ -0,0 +1,149 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "list", + "description": "Returns a list of vector store files.", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store that the files belong to." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "filter", + "description": "Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "in_progress", + "completed", + "failed", + "cancelled" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/files", + "operationId": "listVectorStoreFiles", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + }, + { + "in": "query", + "name": "filter", + "from": "option:filter" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/output.rs new file mode 100644 index 000000000..676d2a16c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/output.rs @@ -0,0 +1,98 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of vector store files.") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store that the files belong to.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ) + .arg( + Arg::new("filter") + .long("filter") + .help("Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "files", "list"] => handle_vector_stores_files_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_files_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/files", runtime::path_escape(vector_store_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + if let Some(v) = m.get_one::("filter") { + query.push(("filter", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/recorded.json new file mode 100644 index 000000000..666956e0c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__list/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "vector-stores", + "files", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/files", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/input.json new file mode 100644 index 000000000..1cab8f87c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/input.json @@ -0,0 +1,149 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "list", + "description": "Returns a list of vector store files.", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store that the files belong to." + } + ], + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "filter", + "description": "Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "in_progress", + "completed", + "failed", + "cancelled" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/files", + "operationId": "listVectorStoreFiles", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + }, + { + "in": "query", + "name": "filter", + "from": "option:filter" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/output.rs new file mode 100644 index 000000000..676d2a16c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/output.rs @@ -0,0 +1,98 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of vector store files.") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store that the files belong to.") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ) + .arg( + Arg::new("filter") + .long("filter") + .help("Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`."), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "files", "list"] => handle_vector_stores_files_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_files_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/files", runtime::path_escape(vector_store_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + if let Some(v) = m.get_one::("filter") { + query.push(("filter", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/recorded.json new file mode 100644 index 000000000..666956e0c --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__retrieve/recorded.json @@ -0,0 +1,15 @@ +{ + "args": [ + "vector-stores", + "files", + "list", + "EXAMPLE" + ], + "request": { + "method": "get", + "path": "/vector_stores/{vector_store_id}/files", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/input.json new file mode 100644 index 000000000..bf660480b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/input.json @@ -0,0 +1,120 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "files", + "commands": [ + { + "name": "create", + "description": "Create a vector store file by attaching a [File](/docs/api-reference/files) to a [vector store](/docs/api-reference/vector-stores/object).", + "arguments": [ + { + "name": "vector-store-id", + "required": true, + "description": "The ID of the vector store for which to create a File.\n" + } + ], + "options": [ + { + "name": "file-id", + "group": "body", + "required": true, + "description": "A [File](/docs/api-reference/files) ID that the vector store should use. Useful for tools like `file_search` that can access files. For multi-file ingestion, we recommend [`file_batches`](/docs/api-reference/vector-stores-file-batches/createBatch) to minimize per-vector-store write requests.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "chunking-strategy", + "group": "body", + "description": "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "attributes", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/vector_stores/{vector_store_id}/files", + "operationId": "createVectorStoreFile", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "vector_store_id", + "from": "argument:vector-store-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "file_id", + "from": "option:file-id", + "jsonPath": "file_id", + "encoding": "string", + "required": true + }, + { + "name": "chunking_strategy", + "from": "option:chunking-strategy", + "jsonPath": "chunking_strategy", + "encoding": "json" + }, + { + "name": "attributes", + "from": "option:attributes", + "jsonPath": "attributes", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/output.rs new file mode 100644 index 000000000..05ce38b61 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/output.rs @@ -0,0 +1,89 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("files") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a vector store file by attaching a [File](/docs/api-reference/files) to a [vector store](/docs/api-reference/vector-stores/object).") + .arg( + Arg::new("vector-store-id") + .help("The ID of the vector store for which to create a File.\n") + .required(true), + ) + .arg( + Arg::new("file-id") + .long("file-id") + .help("A [File](/docs/api-reference/files) ID that the vector store should use. Useful for tools like `file_search` that can access files. For multi-file ingestion, we recommend [`file_batches`](/docs/api-reference/vector-stores-file-batches/createBatch) to minimize per-vector-store write requests.") + .required(true), + ) + .arg( + Arg::new("chunking-strategy") + .long("chunking-strategy") + .help("The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy."), + ) + .arg( + Arg::new("attributes") + .long("attributes"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "files", "create"] => handle_vector_stores_files_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_files_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let vector_store_id = m.get_one::("vector-store-id").map(String::as_str).unwrap_or(""); + let path = format!("/vector_stores/{}/files", runtime::path_escape(vector_store_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("file-id") { + body.insert("file_id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("chunking-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chunking_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("attributes") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("attributes".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/recorded.json new file mode 100644 index 000000000..21c4331f6 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__files__update/recorded.json @@ -0,0 +1,20 @@ +{ + "args": [ + "vector-stores", + "files", + "create", + "EXAMPLE", + "--file-id", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/vector_stores/{vector_store_id}/files", + "query": [], + "bodyFields": [ + "file_id" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/input.json new file mode 100644 index 000000000..0bbed32bc --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/input.json @@ -0,0 +1,110 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "list", + "description": "Returns a list of vector stores.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/vector_stores", + "operationId": "listVectorStores", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/output.rs new file mode 100644 index 000000000..54df78f5f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of vector stores.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "list"] => handle_vector_stores_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/vector_stores".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/recorded.json new file mode 100644 index 000000000..ba755b70a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__list/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "vector-stores", + "list" + ], + "request": { + "method": "get", + "path": "/vector_stores", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/input.json new file mode 100644 index 000000000..0bbed32bc --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/input.json @@ -0,0 +1,110 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "list", + "description": "Returns a list of vector stores.", + "options": [ + { + "name": "limit", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "before", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/vector_stores", + "operationId": "listVectorStores", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + }, + { + "in": "query", + "name": "before", + "from": "option:before" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/output.rs new file mode 100644 index 000000000..54df78f5f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/output.rs @@ -0,0 +1,79 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Returns a list of vector stores.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n"), + ) + .arg( + Arg::new("after") + .long("after") + .help("A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n"), + ) + .arg( + Arg::new("before") + .long("before") + .help("A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "list"] => handle_vector_stores_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/vector_stores".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + if let Some(v) = m.get_one::("before") { + query.push(("before", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/recorded.json new file mode 100644 index 000000000..ba755b70a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__retrieve/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "vector-stores", + "list" + ], + "request": { + "method": "get", + "path": "/vector_stores", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/input.json new file mode 100644 index 000000000..7534c5ecd --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/input.json @@ -0,0 +1,149 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "create", + "description": "Create a vector store.", + "options": [ + { + "name": "file-ids", + "group": "body", + "description": "A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files.", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "name", + "group": "body", + "description": "The name of the vector store.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "description", + "group": "body", + "description": "A description for the vector store. Can be used to describe the vector store's purpose.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "The expiration policy for a vector store.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "chunking-strategy", + "group": "body", + "description": "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/vector_stores", + "operationId": "createVectorStore", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "file_ids", + "from": "option:file-ids", + "jsonPath": "file_ids", + "encoding": "array" + }, + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string" + }, + { + "name": "description", + "from": "option:description", + "jsonPath": "description", + "encoding": "string" + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + }, + { + "name": "chunking_strategy", + "from": "option:chunking-strategy", + "jsonPath": "chunking_strategy", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/output.rs new file mode 100644 index 000000000..ba40c914b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/output.rs @@ -0,0 +1,105 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a vector store.") + .arg( + Arg::new("file-ids") + .long("file-ids") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files."), + ) + .arg( + Arg::new("name") + .long("name") + .help("The name of the vector store."), + ) + .arg( + Arg::new("description") + .long("description") + .help("A description for the vector store. Can be used to describe the vector store's purpose."), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("The expiration policy for a vector store."), + ) + .arg( + Arg::new("chunking-strategy") + .long("chunking-strategy") + .help("The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty."), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "create"] => handle_vector_stores_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/vector_stores".to_string(); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("file-ids") { + body.insert("file_ids".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("description") { + body.insert("description".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + if let Some(v) = m.get_one::("chunking-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chunking_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/recorded.json new file mode 100644 index 000000000..b7112a01e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__search/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "vector-stores", + "create" + ], + "request": { + "method": "post", + "path": "/vector_stores", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/input.json new file mode 100644 index 000000000..7534c5ecd --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/input.json @@ -0,0 +1,149 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "vector-stores", + "commands": [ + { + "name": "create", + "description": "Create a vector store.", + "options": [ + { + "name": "file-ids", + "group": "body", + "description": "A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files.", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "name", + "group": "body", + "description": "The name of the vector store.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "description", + "group": "body", + "description": "A description for the vector store. Can be used to describe the vector store's purpose.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "expires-after", + "group": "body", + "description": "The expiration policy for a vector store.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "chunking-strategy", + "group": "body", + "description": "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "metadata", + "group": "body", + "arguments": [ + { + "name": "value" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/vector_stores", + "operationId": "createVectorStore", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "file_ids", + "from": "option:file-ids", + "jsonPath": "file_ids", + "encoding": "array" + }, + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string" + }, + { + "name": "description", + "from": "option:description", + "jsonPath": "description", + "encoding": "string" + }, + { + "name": "expires_after", + "from": "option:expires-after", + "jsonPath": "expires_after", + "encoding": "json" + }, + { + "name": "chunking_strategy", + "from": "option:chunking-strategy", + "jsonPath": "chunking_strategy", + "encoding": "json" + }, + { + "name": "metadata", + "from": "option:metadata", + "jsonPath": "metadata", + "encoding": "json" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/output.rs new file mode 100644 index 000000000..ba40c914b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/output.rs @@ -0,0 +1,105 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("vector-stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a vector store.") + .arg( + Arg::new("file-ids") + .long("file-ids") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files."), + ) + .arg( + Arg::new("name") + .long("name") + .help("The name of the vector store."), + ) + .arg( + Arg::new("description") + .long("description") + .help("A description for the vector store. Can be used to describe the vector store's purpose."), + ) + .arg( + Arg::new("expires-after") + .long("expires-after") + .help("The expiration policy for a vector store."), + ) + .arg( + Arg::new("chunking-strategy") + .long("chunking-strategy") + .help("The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty."), + ) + .arg( + Arg::new("metadata") + .long("metadata"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["vector-stores", "create"] => handle_vector_stores_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_vector_stores_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/vector_stores".to_string(); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("file-ids") { + body.insert("file_ids".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("description") { + body.insert("description".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("expires-after") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("expires_after".to_string(), value); + } + if let Some(v) = m.get_one::("chunking-strategy") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("chunking_strategy".to_string(), value); + } + if let Some(v) = m.get_one::("metadata") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("metadata".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/recorded.json new file mode 100644 index 000000000..b7112a01e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/vector-stores__update/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "vector-stores", + "create" + ], + "request": { + "method": "post", + "path": "/vector_stores", + "query": [], + "bodyFields": [], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/input.json new file mode 100644 index 000000000..7fdf8d965 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/input.json @@ -0,0 +1,144 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "create", + "description": "Create a new video generation job from a prompt and optional reference assets.", + "options": [ + { + "name": "model", + "group": "body", + "description": "The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "required": true, + "description": "Text prompt that describes the video to generate.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "input-reference", + "group": "body", + "description": "Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "seconds", + "group": "body", + "description": "Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "4", + "8", + "12" + ] + } + ] + }, + { + "name": "size", + "group": "body", + "description": "Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "720x1280", + "1280x720", + "1024x1792", + "1792x1024" + ] + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/videos", + "operationId": "createVideo", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "string", + "required": true + }, + { + "name": "input_reference", + "from": "option:input-reference", + "jsonPath": "input_reference", + "encoding": "json" + }, + { + "name": "seconds", + "from": "option:seconds", + "jsonPath": "seconds", + "encoding": "string" + }, + { + "name": "size", + "from": "option:size", + "jsonPath": "size", + "encoding": "string" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/output.rs new file mode 100644 index 000000000..d2d6da0c5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/output.rs @@ -0,0 +1,95 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new video generation job from a prompt and optional reference assets.") + .arg( + Arg::new("model") + .long("model") + .help("The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`."), + ) + .arg( + Arg::new("prompt") + .long("prompt") + .help("Text prompt that describes the video to generate.") + .required(true), + ) + .arg( + Arg::new("input-reference") + .long("input-reference") + .help("Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`."), + ) + .arg( + Arg::new("seconds") + .long("seconds") + .help("Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds."), + ) + .arg( + Arg::new("size") + .long("size") + .help("Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "create"] => handle_videos_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/videos".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + body.insert("prompt".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("input-reference") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input_reference".to_string(), value); + } + if let Some(v) = m.get_one::("seconds") { + body.insert("seconds".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("size") { + body.insert("size".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/recorded.json new file mode 100644 index 000000000..18ca64452 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create-character/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "videos", + "create", + "--prompt", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/videos", + "query": [], + "bodyFields": [ + "prompt" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/input.json new file mode 100644 index 000000000..7fdf8d965 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/input.json @@ -0,0 +1,144 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "create", + "description": "Create a new video generation job from a prompt and optional reference assets.", + "options": [ + { + "name": "model", + "group": "body", + "description": "The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "required": true, + "description": "Text prompt that describes the video to generate.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "input-reference", + "group": "body", + "description": "Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "seconds", + "group": "body", + "description": "Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "4", + "8", + "12" + ] + } + ] + }, + { + "name": "size", + "group": "body", + "description": "Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "720x1280", + "1280x720", + "1024x1792", + "1792x1024" + ] + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/videos", + "operationId": "createVideo", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "string", + "required": true + }, + { + "name": "input_reference", + "from": "option:input-reference", + "jsonPath": "input_reference", + "encoding": "json" + }, + { + "name": "seconds", + "from": "option:seconds", + "jsonPath": "seconds", + "encoding": "string" + }, + { + "name": "size", + "from": "option:size", + "jsonPath": "size", + "encoding": "string" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/output.rs new file mode 100644 index 000000000..d2d6da0c5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/output.rs @@ -0,0 +1,95 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new video generation job from a prompt and optional reference assets.") + .arg( + Arg::new("model") + .long("model") + .help("The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`."), + ) + .arg( + Arg::new("prompt") + .long("prompt") + .help("Text prompt that describes the video to generate.") + .required(true), + ) + .arg( + Arg::new("input-reference") + .long("input-reference") + .help("Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`."), + ) + .arg( + Arg::new("seconds") + .long("seconds") + .help("Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds."), + ) + .arg( + Arg::new("size") + .long("size") + .help("Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "create"] => handle_videos_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/videos".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + body.insert("prompt".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("input-reference") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input_reference".to_string(), value); + } + if let Some(v) = m.get_one::("seconds") { + body.insert("seconds".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("size") { + body.insert("size".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/recorded.json new file mode 100644 index 000000000..18ca64452 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__create/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "videos", + "create", + "--prompt", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/videos", + "query": [], + "bodyFields": [ + "prompt" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/input.json new file mode 100644 index 000000000..ca9d0f969 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/input.json @@ -0,0 +1,57 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "delete", + "description": "Permanently delete a completed or failed video and its stored assets.", + "arguments": [ + { + "name": "video-id", + "required": true, + "description": "The identifier of the video to delete." + } + ], + "x-openapi": { + "method": "delete", + "path": "/videos/{video_id}", + "operationId": "DeleteVideo", + "params": [ + { + "in": "path", + "name": "video_id", + "from": "argument:video-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/output.rs new file mode 100644 index 000000000..5c8be2581 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/output.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("delete") + .about("Permanently delete a completed or failed video and its stored assets.") + .arg( + Arg::new("video-id") + .help("The identifier of the video to delete.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "delete"] => handle_videos_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let video_id = m.get_one::("video-id").map(String::as_str).unwrap_or(""); + let path = format!("/videos/{}", runtime::path_escape(video_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/recorded.json new file mode 100644 index 000000000..d73f77c0a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__delete/recorded.json @@ -0,0 +1,14 @@ +{ + "args": [ + "videos", + "delete", + "EXAMPLE" + ], + "request": { + "method": "delete", + "path": "/videos/{video_id}", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/input.json new file mode 100644 index 000000000..b24b422b0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/input.json @@ -0,0 +1,95 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "list", + "description": "List recently generated videos for the current project.", + "options": [ + { + "name": "limit", + "description": "Number of items to retrieve", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "Identifier for the last item from the previous pagination request", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/videos", + "operationId": "ListVideos", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/output.rs new file mode 100644 index 000000000..1f96192eb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/output.rs @@ -0,0 +1,71 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List recently generated videos for the current project.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of items to retrieve"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last item from the previous pagination request"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "list"] => handle_videos_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/videos".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/recorded.json new file mode 100644 index 000000000..fb0836800 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__download-content/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "videos", + "list" + ], + "request": { + "method": "get", + "path": "/videos", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/input.json new file mode 100644 index 000000000..7fdf8d965 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/input.json @@ -0,0 +1,144 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "create", + "description": "Create a new video generation job from a prompt and optional reference assets.", + "options": [ + { + "name": "model", + "group": "body", + "description": "The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "required": true, + "description": "Text prompt that describes the video to generate.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "input-reference", + "group": "body", + "description": "Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "seconds", + "group": "body", + "description": "Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "4", + "8", + "12" + ] + } + ] + }, + { + "name": "size", + "group": "body", + "description": "Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "720x1280", + "1280x720", + "1024x1792", + "1792x1024" + ] + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/videos", + "operationId": "createVideo", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "string", + "required": true + }, + { + "name": "input_reference", + "from": "option:input-reference", + "jsonPath": "input_reference", + "encoding": "json" + }, + { + "name": "seconds", + "from": "option:seconds", + "jsonPath": "seconds", + "encoding": "string" + }, + { + "name": "size", + "from": "option:size", + "jsonPath": "size", + "encoding": "string" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/output.rs new file mode 100644 index 000000000..d2d6da0c5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/output.rs @@ -0,0 +1,95 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new video generation job from a prompt and optional reference assets.") + .arg( + Arg::new("model") + .long("model") + .help("The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`."), + ) + .arg( + Arg::new("prompt") + .long("prompt") + .help("Text prompt that describes the video to generate.") + .required(true), + ) + .arg( + Arg::new("input-reference") + .long("input-reference") + .help("Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`."), + ) + .arg( + Arg::new("seconds") + .long("seconds") + .help("Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds."), + ) + .arg( + Arg::new("size") + .long("size") + .help("Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "create"] => handle_videos_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/videos".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + body.insert("prompt".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("input-reference") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input_reference".to_string(), value); + } + if let Some(v) = m.get_one::("seconds") { + body.insert("seconds".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("size") { + body.insert("size".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/recorded.json new file mode 100644 index 000000000..18ca64452 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__edit/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "videos", + "create", + "--prompt", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/videos", + "query": [], + "bodyFields": [ + "prompt" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/input.json new file mode 100644 index 000000000..7fdf8d965 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/input.json @@ -0,0 +1,144 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "create", + "description": "Create a new video generation job from a prompt and optional reference assets.", + "options": [ + { + "name": "model", + "group": "body", + "description": "The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "required": true, + "description": "Text prompt that describes the video to generate.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "input-reference", + "group": "body", + "description": "Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "seconds", + "group": "body", + "description": "Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "4", + "8", + "12" + ] + } + ] + }, + { + "name": "size", + "group": "body", + "description": "Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "720x1280", + "1280x720", + "1024x1792", + "1792x1024" + ] + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/videos", + "operationId": "createVideo", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "string", + "required": true + }, + { + "name": "input_reference", + "from": "option:input-reference", + "jsonPath": "input_reference", + "encoding": "json" + }, + { + "name": "seconds", + "from": "option:seconds", + "jsonPath": "seconds", + "encoding": "string" + }, + { + "name": "size", + "from": "option:size", + "jsonPath": "size", + "encoding": "string" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/output.rs new file mode 100644 index 000000000..d2d6da0c5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/output.rs @@ -0,0 +1,95 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new video generation job from a prompt and optional reference assets.") + .arg( + Arg::new("model") + .long("model") + .help("The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`."), + ) + .arg( + Arg::new("prompt") + .long("prompt") + .help("Text prompt that describes the video to generate.") + .required(true), + ) + .arg( + Arg::new("input-reference") + .long("input-reference") + .help("Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`."), + ) + .arg( + Arg::new("seconds") + .long("seconds") + .help("Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds."), + ) + .arg( + Arg::new("size") + .long("size") + .help("Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "create"] => handle_videos_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/videos".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + body.insert("prompt".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("input-reference") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input_reference".to_string(), value); + } + if let Some(v) = m.get_one::("seconds") { + body.insert("seconds".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("size") { + body.insert("size".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/recorded.json new file mode 100644 index 000000000..18ca64452 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__extend/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "videos", + "create", + "--prompt", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/videos", + "query": [], + "bodyFields": [ + "prompt" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/input.json new file mode 100644 index 000000000..b24b422b0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/input.json @@ -0,0 +1,95 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "list", + "description": "List recently generated videos for the current project.", + "options": [ + { + "name": "limit", + "description": "Number of items to retrieve", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "Identifier for the last item from the previous pagination request", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/videos", + "operationId": "ListVideos", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/output.rs new file mode 100644 index 000000000..1f96192eb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/output.rs @@ -0,0 +1,71 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List recently generated videos for the current project.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of items to retrieve"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last item from the previous pagination request"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "list"] => handle_videos_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/videos".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/recorded.json new file mode 100644 index 000000000..fb0836800 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__get-character/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "videos", + "list" + ], + "request": { + "method": "get", + "path": "/videos", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/input.json new file mode 100644 index 000000000..b24b422b0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/input.json @@ -0,0 +1,95 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "list", + "description": "List recently generated videos for the current project.", + "options": [ + { + "name": "limit", + "description": "Number of items to retrieve", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "Identifier for the last item from the previous pagination request", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/videos", + "operationId": "ListVideos", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/output.rs new file mode 100644 index 000000000..1f96192eb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/output.rs @@ -0,0 +1,71 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List recently generated videos for the current project.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of items to retrieve"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last item from the previous pagination request"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "list"] => handle_videos_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/videos".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/recorded.json new file mode 100644 index 000000000..fb0836800 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__list/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "videos", + "list" + ], + "request": { + "method": "get", + "path": "/videos", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/input.json new file mode 100644 index 000000000..7fdf8d965 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/input.json @@ -0,0 +1,144 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "create", + "description": "Create a new video generation job from a prompt and optional reference assets.", + "options": [ + { + "name": "model", + "group": "body", + "description": "The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`.", + "arguments": [ + { + "name": "value" + } + ] + }, + { + "name": "prompt", + "group": "body", + "required": true, + "description": "Text prompt that describes the video to generate.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "input-reference", + "group": "body", + "description": "Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`.", + "arguments": [ + { + "name": "json" + } + ] + }, + { + "name": "seconds", + "group": "body", + "description": "Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "4", + "8", + "12" + ] + } + ] + }, + { + "name": "size", + "group": "body", + "description": "Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "720x1280", + "1280x720", + "1024x1792", + "1792x1024" + ] + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/videos", + "operationId": "createVideo", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "json" + }, + { + "name": "prompt", + "from": "option:prompt", + "jsonPath": "prompt", + "encoding": "string", + "required": true + }, + { + "name": "input_reference", + "from": "option:input-reference", + "jsonPath": "input_reference", + "encoding": "json" + }, + { + "name": "seconds", + "from": "option:seconds", + "jsonPath": "seconds", + "encoding": "string" + }, + { + "name": "size", + "from": "option:size", + "jsonPath": "size", + "encoding": "string" + } + ] + } + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/output.rs new file mode 100644 index 000000000..d2d6da0c5 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/output.rs @@ -0,0 +1,95 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Create a new video generation job from a prompt and optional reference assets.") + .arg( + Arg::new("model") + .long("model") + .help("The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`."), + ) + .arg( + Arg::new("prompt") + .long("prompt") + .help("Text prompt that describes the video to generate.") + .required(true), + ) + .arg( + Arg::new("input-reference") + .long("input-reference") + .help("Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`."), + ) + .arg( + Arg::new("seconds") + .long("seconds") + .help("Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds."), + ) + .arg( + Arg::new("size") + .long("size") + .help("Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280."), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "create"] => handle_videos_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/videos".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("model".to_string(), value); + } + if let Some(v) = m.get_one::("prompt") { + body.insert("prompt".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("input-reference") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("input_reference".to_string(), value); + } + if let Some(v) = m.get_one::("seconds") { + body.insert("seconds".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("size") { + body.insert("size".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/recorded.json new file mode 100644 index 000000000..18ca64452 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__remix/recorded.json @@ -0,0 +1,18 @@ +{ + "args": [ + "videos", + "create", + "--prompt", + "EXAMPLE" + ], + "request": { + "method": "post", + "path": "/videos", + "query": [], + "bodyFields": [ + "prompt" + ], + "auth": "bearer", + "contentType": "application/json" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/input.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/input.json new file mode 100644 index 000000000..b24b422b0 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/input.json @@ -0,0 +1,95 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "openai", + "version": "2.3.0" + }, + "x-openapi": { + "servers": [ + "https://api.openai.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + }, + { + "type": "http", + "kind": "bearer", + "envVar": "OPENAI_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "videos", + "commands": [ + { + "name": "list", + "description": "List recently generated videos for the current project.", + "options": [ + { + "name": "limit", + "description": "Number of items to retrieve", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "order", + "description": "Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "asc", + "desc" + ] + } + ] + }, + { + "name": "after", + "description": "Identifier for the last item from the previous pagination request", + "group": "query", + "arguments": [ + { + "name": "string" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/videos", + "operationId": "ListVideos", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "order", + "from": "option:order" + }, + { + "in": "query", + "name": "after", + "from": "option:after" + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/output.rs b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/output.rs new file mode 100644 index 000000000..1f96192eb --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/output.rs @@ -0,0 +1,71 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("videos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List recently generated videos for the current project.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Number of items to retrieve"), + ) + .arg( + Arg::new("order") + .long("order") + .help("Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order."), + ) + .arg( + Arg::new("after") + .long("after") + .help("Identifier for the last item from the previous pagination request"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["videos", "list"] => handle_videos_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_videos_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/videos".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("order") { + query.push(("order", v.clone())); + } + if let Some(v) = m.get_one::("after") { + query.push(("after", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/recorded.json b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/recorded.json new file mode 100644 index 000000000..fb0836800 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/-2.complex.openai/videos__retrieve/recorded.json @@ -0,0 +1,13 @@ +{ + "args": [ + "videos", + "list" + ], + "request": { + "method": "get", + "path": "/videos", + "query": [], + "bodyFields": [], + "auth": "bearer" + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/input.json b/packages/xyd-opencli2rust/__fixtures__/1.basic/input.json new file mode 100644 index 000000000..3af78c5fe --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/input.json @@ -0,0 +1,74 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "sample-api", + "version": "1.2.0", + "description": "A tiny sample API for testing openapi2opencli.", + "contact": { + "name": "API Support", + "url": "https://example.com/support", + "email": "support@example.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + } + }, + "x-openapi": { + "servers": [ + "https://api.example.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "SAMPLE_API_API_KEY", + "scheme": "bearer", + "bearerFormat": "JWT" + } + ] + }, + "commands": [ + { + "name": "models", + "commands": [ + { + "name": "list", + "description": "Lists the currently available models.", + "x-openapi": { + "method": "get", + "path": "/models", + "operationId": "listModels" + } + }, + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieves a model instance.", + "arguments": [ + { + "name": "model", + "required": true, + "description": "The ID of the model to use for this request." + } + ], + "x-openapi": { + "method": "get", + "path": "/models/{model}", + "operationId": "retrieveModel", + "params": [ + { + "in": "path", + "name": "model", + "from": "argument:model", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/.gitignore b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/.gitignore @@ -0,0 +1 @@ +/target diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/Cargo.toml b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/Cargo.toml new file mode 100644 index 000000000..40f84818b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/Cargo.toml @@ -0,0 +1,17 @@ +# Generated once by opencli2rust — yours to edit; regeneration never overwrites it. + +[package] +name = "sample_api" +version = "1.2.0" +edition = "2021" + +[dependencies] +clap = { version = "4", features = ["string"] } +percent-encoding = "2" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +serde_json = "1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } + +[[bin]] +name = "sample-api" +path = "src/main.rs" diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/custom/mod.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/custom/mod.rs new file mode 100644 index 000000000..f6944805d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/custom/mod.rs @@ -0,0 +1,29 @@ +//! Yours — scaffolded once by opencli2rust, never overwritten by regeneration. +//! +//! Two extension seams, both optional: +//! * `CliOverrides` on [`Custom`] — hook/override every generated command +//! (see src/gen/runtime/overrides.rs for the available methods). +//! * [`register`] — add fully custom commands anywhere in the tree; a command +//! registered on an existing path takes over that command's behavior. + +use crate::gen::runtime::{CliOverrides, CustomCommands}; + +/// Override only what you need — every trait method has a default. +pub struct Custom; + +impl CliOverrides for Custom {} + +/// Register hand-written commands. Called by the generated `main`. +pub fn register(commands: &mut CustomCommands) { + let _ = commands; + // Example — a fully custom `tools hello` subcommand: + // + // commands.add(&["tools"], clap::Command::new("hello").about("Say hello"), |_ctx, _m| async move { + // println!("hello"); + // Ok(()) + // }); +} + +pub fn overrides() -> Custom { + Custom +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cli.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cli.rs new file mode 100644 index 000000000..f3c0c3be8 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cli.rs @@ -0,0 +1,53 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::ArgMatches; + +use super::cmd; +use super::runtime::{CliOverrides, Context, CustomCommands}; + +/// The generated root command tree. +pub fn root_command() -> clap::Command { + clap::Command::new("sample-api") + .about("A tiny sample API for testing openapi2opencli.") + .version("1.2.0") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(cmd::models::command()) +} + +/// Parse args, resolve the invoked command path, and dispatch — custom commands +/// first (so user registrations can override generated behavior), then the +/// generated handlers. +pub async fn run(o: O, customs: CustomCommands) -> ExitCode { + let matches = customs.graft(root_command()).get_matches(); + let ctx = Context::from_env(); + let (cmd_path, leaf) = descend(&matches); + + if let Some(handler) = customs.find(&cmd_path) { + return match handler(ctx.clone(), leaf.clone()).await { + Ok(()) => ExitCode::SUCCESS, + Err(err) => o.print_error(&cmd_path, &err), + }; + } + + match cmd_path.first().map(String::as_str) { + Some("models") => cmd::models::run(&ctx, &o, &cmd_path, leaf).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +/// Walk matches to the invoked leaf, collecting the full command path. +fn descend(matches: &ArgMatches) -> (Vec, &ArgMatches) { + let mut path = Vec::new(); + let mut current = matches; + while let Some((name, sub)) = current.subcommand() { + path.push(name.to_string()); + current = sub; + } + (path, current) +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cmd/mod.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cmd/mod.rs new file mode 100644 index 000000000..9ca39335e --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cmd/mod.rs @@ -0,0 +1,3 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod models; diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cmd/models.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cmd/models.rs new file mode 100644 index 000000000..b0678a41a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/cmd/models.rs @@ -0,0 +1,74 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("models") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Lists the currently available models."), + ) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieves a model instance.") + .arg( + Arg::new("model") + .help("The ID of the model to use for this request.") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["models", "list"] => handle_models_list(ctx, o, cmd_path, m).await, + ["models", "retrieve"] => handle_models_retrieve(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_models_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/models".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_models_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let model = m.get_one::("model").map(String::as_str).unwrap_or(""); + let path = format!("/models/{}", runtime::path_escape(model)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/mod.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/mod.rs new file mode 100644 index 000000000..3906dd730 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/mod.rs @@ -0,0 +1,5 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod cli; +pub mod cmd; +pub mod runtime; diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/config.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/config.rs new file mode 100644 index 000000000..9d18cfd54 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/config.rs @@ -0,0 +1,23 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Spec-derived configuration: the API base URL and environment-based auth. + +const DEFAULT_BASE_URL: &str = "https://api.example.com/v1"; + +/// The API base URL, overridable via `SAMPLE_API_BASE_URL`. +pub fn base_url() -> String { + match std::env::var("SAMPLE_API_BASE_URL") { + Ok(v) if !v.is_empty() => v, + _ => DEFAULT_BASE_URL.to_string(), + } +} + +/// Attach credentials read from the environment to the request. +pub fn apply_auth(mut rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder { + if let Ok(v) = std::env::var("SAMPLE_API_API_KEY") { + if !v.is_empty() { + rb = rb.bearer_auth(v); + } + } + rb +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/custom.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/custom.rs new file mode 100644 index 000000000..693254f72 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/custom.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::future::Future; +use std::pin::Pin; + +use clap::{ArgMatches, Command}; + +use super::http::Error; +use super::Context; + +/// The boxed async handler for one custom command (owned inputs, so plain +/// `|ctx, m| async move { ... }` closures fit without lifetime plumbing). +pub type CustomHandler = Box Pin>>>>; + +struct CustomEntry { + under: Vec, + command: Command, + handler: CustomHandler, +} + +/// Hand-written commands registered from `src/custom/mod.rs`, grafted into the +/// generated tree before parsing and dispatched BEFORE generated handlers — a +/// custom command registered on an existing path overrides its behavior. +#[derive(Default)] +pub struct CustomCommands { + entries: Vec, +} + +impl CustomCommands { + pub fn new() -> Self { + Self::default() + } + + /// Add `command` under a parent path (`&[]` = top level): + /// `add(&["tools"], Command::new("hello"), |ctx, m| async move { ... })` + /// registers ` tools hello`. Missing parents are created. When the full + /// path already exists in the generated tree, the generated clap definition + /// keeps handling parsing/help, but YOUR handler runs instead. + #[allow(dead_code)] // user-facing API, called from src/custom/mod.rs + pub fn add(&mut self, under: &[&str], command: Command, handler: H) + where + H: Fn(Context, ArgMatches) -> F + 'static, + F: Future> + 'static, + { + self.entries.push(CustomEntry { + under: under.iter().map(|s| s.to_string()).collect(), + command, + handler: Box::new(move |ctx, m| Box::pin(handler(ctx, m))), + }); + } + + /// Graft registered commands into the generated clap tree (new paths only — + /// existing definitions are left untouched; dispatch still prefers custom). + pub(crate) fn graft(&self, mut root: Command) -> Command { + for entry in &self.entries { + let mut full = entry.under.clone(); + full.push(entry.command.get_name().to_string()); + if path_exists(&root, &full) { + continue; + } + root = graft_at(root, &entry.under, entry.command.clone()); + } + root + } + + /// The handler registered for exactly this invoked command path, if any. + pub(crate) fn find(&self, path: &[String]) -> Option<&CustomHandler> { + self.entries + .iter() + .find(|e| { + e.under.len() + 1 == path.len() + && e.under.iter().zip(path.iter()).all(|(a, b)| a == b) + && path.last().map(String::as_str) == Some(e.command.get_name()) + }) + .map(|e| &e.handler) + } +} + +fn path_exists(root: &Command, path: &[String]) -> bool { + let mut current = root; + for name in path { + match current.find_subcommand(name) { + Some(sub) => current = sub, + None => return false, + } + } + true +} + +fn graft_at(parent: Command, under: &[String], cmd: Command) -> Command { + match under.split_first() { + None => parent.subcommand(cmd), + Some((head, rest)) => { + let rest = rest.to_vec(); + if parent.find_subcommand(head).is_some() { + parent.mut_subcommand(head.clone(), move |sub| graft_at(sub, &rest, cmd)) + } else { + let node = Command::new(head.clone()).subcommand_required(true).arg_required_else_help(true); + parent.subcommand(graft_at(node, &rest, cmd)) + } + } + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/http.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/http.rs new file mode 100644 index 000000000..9ee976257 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/http.rs @@ -0,0 +1,50 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Static HTTP primitives shared by every command handler. + +use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; +use serde_json::Value; + +/// Escape a value for use inside a URL path segment (strict RFC 3986 unreserved). +const PATH_SEGMENT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'_').remove(b'.').remove(b'~'); + +pub fn path_escape(value: &str) -> String { + utf8_percent_encode(value, PATH_SEGMENT).to_string() +} + +/// A generic API request assembled by a command handler. +pub struct Request { + pub method: &'static str, + pub path: String, + pub query: Vec<(&'static str, String)>, + pub headers: Vec<(&'static str, String)>, + pub body: Option>, +} + +/// A request failure, inspectable from `CliOverrides::recover_error` / `print_error`. +#[derive(Debug)] +pub enum Error { + /// The server answered with a 4xx/5xx status; `body` is the decoded response. + Http { status: u16, body: Value }, + /// The request could not be sent or the response could not be read. + Transport(String), + /// A handler-side failure (flag decoding, body assembly, custom command logic). + Invalid(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Http { status, body } => write!(f, "request failed: {status}: {body}"), + Error::Transport(message) => write!(f, "request error: {message}"), + Error::Invalid(message) => write!(f, "{message}"), + } + } +} + +impl std::error::Error for Error {} + +/// Decode a response body: JSON when it parses, the raw text otherwise. +pub(crate) fn decode(bytes: &[u8]) -> Value { + serde_json::from_slice(bytes).unwrap_or_else(|_| Value::String(String::from_utf8_lossy(bytes).into_owned())) +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/mod.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/mod.rs new file mode 100644 index 000000000..f03c99a28 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/mod.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! The vendored runtime: request execution context, override hooks, and the +//! custom-command registry. Extension points live in `overrides.rs` (the +//! `CliOverrides` trait) and `custom.rs` (the `CustomCommands` registry). + +pub mod config; +mod custom; +mod http; +mod overrides; + +pub use custom::CustomCommands; +pub use http::{path_escape, Error, Request}; +pub use overrides::CliOverrides; + +use std::process::ExitCode; + +use serde_json::Value; + +/// Everything a handler (generated or custom) needs to reach the API: the +/// configured base URL and an HTTP client with the spec's auth applied. +#[derive(Clone)] +pub struct Context { + pub base_url: String, + http: reqwest::Client, +} + +impl Context { + pub fn from_env() -> Self { + Self { + base_url: config::base_url(), + http: reqwest::Client::new(), + } + } + + /// Execute against the configured base URL with the spec's auth applied, + /// decoding the response (JSON when it parses, raw text otherwise). + pub async fn execute(&self, req: Request) -> Result { + let (status, bytes) = self.send(req).await?; + let value = http::decode(&bytes); + if status >= 400 { + return Err(Error::Http { status, body: value }); + } + Ok(value) + } + + /// Raw-bytes variant of [`execute`](Self::execute) for binary/non-JSON endpoints. + #[allow(dead_code)] // user-facing API for custom command handlers + pub async fn execute_raw(&self, req: Request) -> Result, Error> { + let (status, bytes) = self.send(req).await?; + if status >= 400 { + return Err(Error::Http { status, body: http::decode(&bytes) }); + } + Ok(bytes) + } + + async fn send(&self, req: Request) -> Result<(u16, Vec), Error> { + let endpoint = format!("{}{}", self.base_url.trim_end_matches('/'), req.path); + let method = reqwest::Method::from_bytes(req.method.as_bytes()).map_err(|e| Error::Invalid(e.to_string()))?; + let mut rb = self.http.request(method, &endpoint); + if !req.query.is_empty() { + rb = rb.query(&req.query); + } + let has_content_type = req.headers.iter().any(|(k, _)| k.eq_ignore_ascii_case("content-type")); + for (key, value) in &req.headers { + rb = rb.header(*key, value.as_str()); + } + if let Some(body) = req.body { + if !has_content_type { + rb = rb.header("Content-Type", "application/json"); + } + rb = rb.body(body); + } + rb = config::apply_auth(rb); + let resp = rb.send().await.map_err(|e| Error::Transport(e.to_string()))?; + let status = resp.status().as_u16(); + let bytes = resp.bytes().await.map_err(|e| Error::Transport(e.to_string()))?.to_vec(); + Ok((status, bytes)) + } +} + +/// Thread one generated request through the override hooks: mutate, execute, +/// transform, recover, then print. Printing happens ONLY here (via the trait), +/// so overrides fully control the CLI's output. +pub async fn run_request( + ctx: &Context, + o: &O, + cmd_path: &[String], + mut req: Request, +) -> ExitCode { + o.before_request(ctx, cmd_path, &mut req); + let outcome = match ctx.execute(req).await { + Ok(value) => o.transform_response(ctx, cmd_path, value), + Err(err) => Err(err), + }; + match outcome.or_else(|err| o.recover_error(ctx, cmd_path, err)) { + Ok(value) => { + o.print_success(cmd_path, &value); + ExitCode::SUCCESS + } + Err(err) => o.print_error(cmd_path, &err), + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/overrides.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/overrides.rs new file mode 100644 index 000000000..e1af75754 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/gen/runtime/overrides.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use serde_json::Value; + +use super::http::{Error, Request}; +use super::Context; + +/// The override seam for every generated command. Implement this on your type in +/// `src/custom/mod.rs` — every method has a default, so `impl CliOverrides for +/// Custom {}` compiles and behaves exactly like the stock CLI. +pub trait CliOverrides { + /// Mutate the assembled request before it is sent (extra headers, alternate + /// auth, tracing). `cmd_path` is the invoked command path, e.g. `["models", "list"]`. + fn before_request(&self, ctx: &Context, cmd_path: &[String], req: &mut Request) { + let _ = (ctx, cmd_path, req); + } + + /// Transform the decoded response before printing. + fn transform_response(&self, ctx: &Context, cmd_path: &[String], value: Value) -> Result { + let _ = (ctx, cmd_path); + Ok(value) + } + + /// Recover from a failure: return `Ok(value)` to turn it into a success, + /// or hand the error back to keep failing. + fn recover_error(&self, ctx: &Context, cmd_path: &[String], err: Error) -> Result { + let _ = (ctx, cmd_path); + Err(err) + } + + /// Print a successful result. Default: pretty JSON (raw text for non-JSON bodies). + fn print_success(&self, cmd_path: &[String], value: &Value) { + let _ = cmd_path; + match value { + Value::String(text) => println!("{text}"), + other => match serde_json::to_string_pretty(other) { + Ok(text) => println!("{text}"), + Err(_) => println!("{other}"), + }, + } + } + + /// Print a failure and choose the process exit code. + fn print_error(&self, cmd_path: &[String], err: &Error) -> ExitCode { + let _ = cmd_path; + eprintln!("error: {err}"); + ExitCode::FAILURE + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/main.rs b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/main.rs new file mode 100644 index 000000000..99acaa6ae --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/1.basic/output/src/main.rs @@ -0,0 +1,11 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +mod custom; +mod gen; + +#[tokio::main] +async fn main() -> std::process::ExitCode { + let mut commands = gen::runtime::CustomCommands::new(); + custom::register(&mut commands); + gen::cli::run(custom::overrides(), commands).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/input.json b/packages/xyd-opencli2rust/__fixtures__/2.crud/input.json new file mode 100644 index 000000000..e763419c6 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/input.json @@ -0,0 +1,281 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "widgets-api", + "version": "2.0.0" + }, + "x-openapi": { + "servers": [ + "https://api.widgets.test" + ], + "security": [ + { + "type": "apiKey", + "kind": "apiKey-header", + "envVar": "WIDGETS_API_API_KEY", + "in": "header", + "name": "X-Api-Key" + } + ] + }, + "commands": [ + { + "name": "widgets", + "commands": [ + { + "name": "list", + "description": "List all widgets.", + "options": [ + { + "name": "limit", + "description": "Maximum number of widgets to return.", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + }, + { + "name": "status", + "description": "Filter by status.", + "group": "query", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "active", + "archived", + "draft" + ] + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/widgets", + "operationId": "listWidgets", + "params": [ + { + "in": "query", + "name": "limit", + "from": "option:limit" + }, + { + "in": "query", + "name": "status", + "from": "option:status" + } + ] + } + }, + { + "name": "create", + "description": "Create a widget.", + "options": [ + { + "name": "name", + "group": "body", + "required": true, + "description": "The widget name.", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "color", + "group": "body", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "red", + "green", + "blue" + ] + } + ] + }, + { + "name": "tags", + "group": "body", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "enabled", + "group": "body" + }, + { + "name": "config", + "group": "body", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/widgets", + "operationId": "createWidget", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string", + "required": true + }, + { + "name": "color", + "from": "option:color", + "jsonPath": "color", + "encoding": "string" + }, + { + "name": "tags", + "from": "option:tags", + "jsonPath": "tags", + "encoding": "array" + }, + { + "name": "enabled", + "from": "option:enabled", + "jsonPath": "enabled", + "encoding": "boolean" + }, + { + "name": "config", + "from": "option:config", + "jsonPath": "config", + "encoding": "json" + } + ] + } + } + }, + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieve a widget.", + "arguments": [ + { + "name": "widget-id", + "required": true + } + ], + "x-openapi": { + "method": "get", + "path": "/widgets/{widget_id}", + "operationId": "retrieveWidget", + "params": [ + { + "in": "path", + "name": "widget_id", + "from": "argument:widget-id", + "required": true + } + ] + } + }, + { + "name": "update", + "description": "Update a widget.", + "arguments": [ + { + "name": "widget-id", + "required": true + } + ], + "options": [ + { + "name": "name", + "group": "body", + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "enabled", + "group": "body" + } + ], + "x-openapi": { + "method": "patch", + "path": "/widgets/{widget_id}", + "operationId": "updateWidget", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "widget_id", + "from": "argument:widget-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "name", + "from": "option:name", + "jsonPath": "name", + "encoding": "string" + }, + { + "name": "enabled", + "from": "option:enabled", + "jsonPath": "enabled", + "encoding": "boolean" + } + ] + } + } + }, + { + "name": "delete", + "description": "Delete a widget.", + "arguments": [ + { + "name": "widget-id", + "required": true + } + ], + "x-openapi": { + "method": "delete", + "path": "/widgets/{widget_id}", + "operationId": "deleteWidget", + "params": [ + { + "in": "path", + "name": "widget_id", + "from": "argument:widget-id", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/.gitignore b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/.gitignore @@ -0,0 +1 @@ +/target diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/Cargo.toml b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/Cargo.toml new file mode 100644 index 000000000..658cc385f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/Cargo.toml @@ -0,0 +1,17 @@ +# Generated once by opencli2rust — yours to edit; regeneration never overwrites it. + +[package] +name = "widgets_api" +version = "2.0.0" +edition = "2021" + +[dependencies] +clap = { version = "4", features = ["string"] } +percent-encoding = "2" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +serde_json = "1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } + +[[bin]] +name = "widgets-api" +path = "src/main.rs" diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/custom/mod.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/custom/mod.rs new file mode 100644 index 000000000..f6944805d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/custom/mod.rs @@ -0,0 +1,29 @@ +//! Yours — scaffolded once by opencli2rust, never overwritten by regeneration. +//! +//! Two extension seams, both optional: +//! * `CliOverrides` on [`Custom`] — hook/override every generated command +//! (see src/gen/runtime/overrides.rs for the available methods). +//! * [`register`] — add fully custom commands anywhere in the tree; a command +//! registered on an existing path takes over that command's behavior. + +use crate::gen::runtime::{CliOverrides, CustomCommands}; + +/// Override only what you need — every trait method has a default. +pub struct Custom; + +impl CliOverrides for Custom {} + +/// Register hand-written commands. Called by the generated `main`. +pub fn register(commands: &mut CustomCommands) { + let _ = commands; + // Example — a fully custom `tools hello` subcommand: + // + // commands.add(&["tools"], clap::Command::new("hello").about("Say hello"), |_ctx, _m| async move { + // println!("hello"); + // Ok(()) + // }); +} + +pub fn overrides() -> Custom { + Custom +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cli.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cli.rs new file mode 100644 index 000000000..784d0d8e4 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cli.rs @@ -0,0 +1,52 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::ArgMatches; + +use super::cmd; +use super::runtime::{CliOverrides, Context, CustomCommands}; + +/// The generated root command tree. +pub fn root_command() -> clap::Command { + clap::Command::new("widgets-api") + .version("2.0.0") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(cmd::widgets::command()) +} + +/// Parse args, resolve the invoked command path, and dispatch — custom commands +/// first (so user registrations can override generated behavior), then the +/// generated handlers. +pub async fn run(o: O, customs: CustomCommands) -> ExitCode { + let matches = customs.graft(root_command()).get_matches(); + let ctx = Context::from_env(); + let (cmd_path, leaf) = descend(&matches); + + if let Some(handler) = customs.find(&cmd_path) { + return match handler(ctx.clone(), leaf.clone()).await { + Ok(()) => ExitCode::SUCCESS, + Err(err) => o.print_error(&cmd_path, &err), + }; + } + + match cmd_path.first().map(String::as_str) { + Some("widgets") => cmd::widgets::run(&ctx, &o, &cmd_path, leaf).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +/// Walk matches to the invoked leaf, collecting the full command path. +fn descend(matches: &ArgMatches) -> (Vec, &ArgMatches) { + let mut path = Vec::new(); + let mut current = matches; + while let Some((name, sub)) = current.subcommand() { + path.push(name.to_string()); + current = sub; + } + (path, current) +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cmd/mod.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cmd/mod.rs new file mode 100644 index 000000000..40aabe059 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cmd/mod.rs @@ -0,0 +1,3 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod widgets; diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cmd/widgets.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cmd/widgets.rs new file mode 100644 index 000000000..8b17cbe43 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/cmd/widgets.rs @@ -0,0 +1,240 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("widgets") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List all widgets.") + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)) + .help("Maximum number of widgets to return."), + ) + .arg( + Arg::new("status") + .long("status") + .help("Filter by status."), + ), + ) + .subcommand( + Command::new("create") + .about("Create a widget.") + .arg( + Arg::new("name") + .long("name") + .help("The widget name.") + .required(true), + ) + .arg( + Arg::new("color") + .long("color"), + ) + .arg( + Arg::new("tags") + .long("tags") + .action(clap::ArgAction::Append) + .value_delimiter(','), + ) + .arg( + Arg::new("enabled") + .long("enabled") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)), + ) + .arg( + Arg::new("config") + .long("config"), + ), + ) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieve a widget.") + .arg( + Arg::new("widget-id") + .required(true), + ), + ) + .subcommand( + Command::new("update") + .about("Update a widget.") + .arg( + Arg::new("widget-id") + .required(true), + ) + .arg( + Arg::new("name") + .long("name"), + ) + .arg( + Arg::new("enabled") + .long("enabled") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)), + ), + ) + .subcommand( + Command::new("delete") + .about("Delete a widget.") + .arg( + Arg::new("widget-id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["widgets", "list"] => handle_widgets_list(ctx, o, cmd_path, m).await, + ["widgets", "create"] => handle_widgets_create(ctx, o, cmd_path, m).await, + ["widgets", "retrieve"] => handle_widgets_retrieve(ctx, o, cmd_path, m).await, + ["widgets", "update"] => handle_widgets_update(ctx, o, cmd_path, m).await, + ["widgets", "delete"] => handle_widgets_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_widgets_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/widgets".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + if let Some(v) = m.get_one::("status") { + query.push(("status", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_widgets_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/widgets".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("color") { + body.insert("color".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("tags") { + body.insert("tags".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("enabled") { + body.insert("enabled".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("config") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("config".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_widgets_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let widget_id = m.get_one::("widget-id").map(String::as_str).unwrap_or(""); + let path = format!("/widgets/{}", runtime::path_escape(widget_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_widgets_update( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let widget_id = m.get_one::("widget-id").map(String::as_str).unwrap_or(""); + let path = format!("/widgets/{}", runtime::path_escape(widget_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("enabled") { + body.insert("enabled".to_string(), serde_json::Value::Bool(*v)); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "PATCH", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_widgets_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let widget_id = m.get_one::("widget-id").map(String::as_str).unwrap_or(""); + let path = format!("/widgets/{}", runtime::path_escape(widget_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/mod.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/mod.rs new file mode 100644 index 000000000..3906dd730 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/mod.rs @@ -0,0 +1,5 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod cli; +pub mod cmd; +pub mod runtime; diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/config.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/config.rs new file mode 100644 index 000000000..9f7e0914b --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/config.rs @@ -0,0 +1,23 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Spec-derived configuration: the API base URL and environment-based auth. + +const DEFAULT_BASE_URL: &str = "https://api.widgets.test"; + +/// The API base URL, overridable via `WIDGETS_API_BASE_URL`. +pub fn base_url() -> String { + match std::env::var("WIDGETS_API_BASE_URL") { + Ok(v) if !v.is_empty() => v, + _ => DEFAULT_BASE_URL.to_string(), + } +} + +/// Attach credentials read from the environment to the request. +pub fn apply_auth(mut rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder { + if let Ok(v) = std::env::var("WIDGETS_API_API_KEY") { + if !v.is_empty() { + rb = rb.header("X-Api-Key", v); + } + } + rb +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/custom.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/custom.rs new file mode 100644 index 000000000..693254f72 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/custom.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::future::Future; +use std::pin::Pin; + +use clap::{ArgMatches, Command}; + +use super::http::Error; +use super::Context; + +/// The boxed async handler for one custom command (owned inputs, so plain +/// `|ctx, m| async move { ... }` closures fit without lifetime plumbing). +pub type CustomHandler = Box Pin>>>>; + +struct CustomEntry { + under: Vec, + command: Command, + handler: CustomHandler, +} + +/// Hand-written commands registered from `src/custom/mod.rs`, grafted into the +/// generated tree before parsing and dispatched BEFORE generated handlers — a +/// custom command registered on an existing path overrides its behavior. +#[derive(Default)] +pub struct CustomCommands { + entries: Vec, +} + +impl CustomCommands { + pub fn new() -> Self { + Self::default() + } + + /// Add `command` under a parent path (`&[]` = top level): + /// `add(&["tools"], Command::new("hello"), |ctx, m| async move { ... })` + /// registers ` tools hello`. Missing parents are created. When the full + /// path already exists in the generated tree, the generated clap definition + /// keeps handling parsing/help, but YOUR handler runs instead. + #[allow(dead_code)] // user-facing API, called from src/custom/mod.rs + pub fn add(&mut self, under: &[&str], command: Command, handler: H) + where + H: Fn(Context, ArgMatches) -> F + 'static, + F: Future> + 'static, + { + self.entries.push(CustomEntry { + under: under.iter().map(|s| s.to_string()).collect(), + command, + handler: Box::new(move |ctx, m| Box::pin(handler(ctx, m))), + }); + } + + /// Graft registered commands into the generated clap tree (new paths only — + /// existing definitions are left untouched; dispatch still prefers custom). + pub(crate) fn graft(&self, mut root: Command) -> Command { + for entry in &self.entries { + let mut full = entry.under.clone(); + full.push(entry.command.get_name().to_string()); + if path_exists(&root, &full) { + continue; + } + root = graft_at(root, &entry.under, entry.command.clone()); + } + root + } + + /// The handler registered for exactly this invoked command path, if any. + pub(crate) fn find(&self, path: &[String]) -> Option<&CustomHandler> { + self.entries + .iter() + .find(|e| { + e.under.len() + 1 == path.len() + && e.under.iter().zip(path.iter()).all(|(a, b)| a == b) + && path.last().map(String::as_str) == Some(e.command.get_name()) + }) + .map(|e| &e.handler) + } +} + +fn path_exists(root: &Command, path: &[String]) -> bool { + let mut current = root; + for name in path { + match current.find_subcommand(name) { + Some(sub) => current = sub, + None => return false, + } + } + true +} + +fn graft_at(parent: Command, under: &[String], cmd: Command) -> Command { + match under.split_first() { + None => parent.subcommand(cmd), + Some((head, rest)) => { + let rest = rest.to_vec(); + if parent.find_subcommand(head).is_some() { + parent.mut_subcommand(head.clone(), move |sub| graft_at(sub, &rest, cmd)) + } else { + let node = Command::new(head.clone()).subcommand_required(true).arg_required_else_help(true); + parent.subcommand(graft_at(node, &rest, cmd)) + } + } + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/http.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/http.rs new file mode 100644 index 000000000..9ee976257 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/http.rs @@ -0,0 +1,50 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Static HTTP primitives shared by every command handler. + +use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; +use serde_json::Value; + +/// Escape a value for use inside a URL path segment (strict RFC 3986 unreserved). +const PATH_SEGMENT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'_').remove(b'.').remove(b'~'); + +pub fn path_escape(value: &str) -> String { + utf8_percent_encode(value, PATH_SEGMENT).to_string() +} + +/// A generic API request assembled by a command handler. +pub struct Request { + pub method: &'static str, + pub path: String, + pub query: Vec<(&'static str, String)>, + pub headers: Vec<(&'static str, String)>, + pub body: Option>, +} + +/// A request failure, inspectable from `CliOverrides::recover_error` / `print_error`. +#[derive(Debug)] +pub enum Error { + /// The server answered with a 4xx/5xx status; `body` is the decoded response. + Http { status: u16, body: Value }, + /// The request could not be sent or the response could not be read. + Transport(String), + /// A handler-side failure (flag decoding, body assembly, custom command logic). + Invalid(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Http { status, body } => write!(f, "request failed: {status}: {body}"), + Error::Transport(message) => write!(f, "request error: {message}"), + Error::Invalid(message) => write!(f, "{message}"), + } + } +} + +impl std::error::Error for Error {} + +/// Decode a response body: JSON when it parses, the raw text otherwise. +pub(crate) fn decode(bytes: &[u8]) -> Value { + serde_json::from_slice(bytes).unwrap_or_else(|_| Value::String(String::from_utf8_lossy(bytes).into_owned())) +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/mod.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/mod.rs new file mode 100644 index 000000000..f03c99a28 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/mod.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! The vendored runtime: request execution context, override hooks, and the +//! custom-command registry. Extension points live in `overrides.rs` (the +//! `CliOverrides` trait) and `custom.rs` (the `CustomCommands` registry). + +pub mod config; +mod custom; +mod http; +mod overrides; + +pub use custom::CustomCommands; +pub use http::{path_escape, Error, Request}; +pub use overrides::CliOverrides; + +use std::process::ExitCode; + +use serde_json::Value; + +/// Everything a handler (generated or custom) needs to reach the API: the +/// configured base URL and an HTTP client with the spec's auth applied. +#[derive(Clone)] +pub struct Context { + pub base_url: String, + http: reqwest::Client, +} + +impl Context { + pub fn from_env() -> Self { + Self { + base_url: config::base_url(), + http: reqwest::Client::new(), + } + } + + /// Execute against the configured base URL with the spec's auth applied, + /// decoding the response (JSON when it parses, raw text otherwise). + pub async fn execute(&self, req: Request) -> Result { + let (status, bytes) = self.send(req).await?; + let value = http::decode(&bytes); + if status >= 400 { + return Err(Error::Http { status, body: value }); + } + Ok(value) + } + + /// Raw-bytes variant of [`execute`](Self::execute) for binary/non-JSON endpoints. + #[allow(dead_code)] // user-facing API for custom command handlers + pub async fn execute_raw(&self, req: Request) -> Result, Error> { + let (status, bytes) = self.send(req).await?; + if status >= 400 { + return Err(Error::Http { status, body: http::decode(&bytes) }); + } + Ok(bytes) + } + + async fn send(&self, req: Request) -> Result<(u16, Vec), Error> { + let endpoint = format!("{}{}", self.base_url.trim_end_matches('/'), req.path); + let method = reqwest::Method::from_bytes(req.method.as_bytes()).map_err(|e| Error::Invalid(e.to_string()))?; + let mut rb = self.http.request(method, &endpoint); + if !req.query.is_empty() { + rb = rb.query(&req.query); + } + let has_content_type = req.headers.iter().any(|(k, _)| k.eq_ignore_ascii_case("content-type")); + for (key, value) in &req.headers { + rb = rb.header(*key, value.as_str()); + } + if let Some(body) = req.body { + if !has_content_type { + rb = rb.header("Content-Type", "application/json"); + } + rb = rb.body(body); + } + rb = config::apply_auth(rb); + let resp = rb.send().await.map_err(|e| Error::Transport(e.to_string()))?; + let status = resp.status().as_u16(); + let bytes = resp.bytes().await.map_err(|e| Error::Transport(e.to_string()))?.to_vec(); + Ok((status, bytes)) + } +} + +/// Thread one generated request through the override hooks: mutate, execute, +/// transform, recover, then print. Printing happens ONLY here (via the trait), +/// so overrides fully control the CLI's output. +pub async fn run_request( + ctx: &Context, + o: &O, + cmd_path: &[String], + mut req: Request, +) -> ExitCode { + o.before_request(ctx, cmd_path, &mut req); + let outcome = match ctx.execute(req).await { + Ok(value) => o.transform_response(ctx, cmd_path, value), + Err(err) => Err(err), + }; + match outcome.or_else(|err| o.recover_error(ctx, cmd_path, err)) { + Ok(value) => { + o.print_success(cmd_path, &value); + ExitCode::SUCCESS + } + Err(err) => o.print_error(cmd_path, &err), + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/overrides.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/overrides.rs new file mode 100644 index 000000000..e1af75754 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/gen/runtime/overrides.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use serde_json::Value; + +use super::http::{Error, Request}; +use super::Context; + +/// The override seam for every generated command. Implement this on your type in +/// `src/custom/mod.rs` — every method has a default, so `impl CliOverrides for +/// Custom {}` compiles and behaves exactly like the stock CLI. +pub trait CliOverrides { + /// Mutate the assembled request before it is sent (extra headers, alternate + /// auth, tracing). `cmd_path` is the invoked command path, e.g. `["models", "list"]`. + fn before_request(&self, ctx: &Context, cmd_path: &[String], req: &mut Request) { + let _ = (ctx, cmd_path, req); + } + + /// Transform the decoded response before printing. + fn transform_response(&self, ctx: &Context, cmd_path: &[String], value: Value) -> Result { + let _ = (ctx, cmd_path); + Ok(value) + } + + /// Recover from a failure: return `Ok(value)` to turn it into a success, + /// or hand the error back to keep failing. + fn recover_error(&self, ctx: &Context, cmd_path: &[String], err: Error) -> Result { + let _ = (ctx, cmd_path); + Err(err) + } + + /// Print a successful result. Default: pretty JSON (raw text for non-JSON bodies). + fn print_success(&self, cmd_path: &[String], value: &Value) { + let _ = cmd_path; + match value { + Value::String(text) => println!("{text}"), + other => match serde_json::to_string_pretty(other) { + Ok(text) => println!("{text}"), + Err(_) => println!("{other}"), + }, + } + } + + /// Print a failure and choose the process exit code. + fn print_error(&self, cmd_path: &[String], err: &Error) -> ExitCode { + let _ = cmd_path; + eprintln!("error: {err}"); + ExitCode::FAILURE + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/main.rs b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/main.rs new file mode 100644 index 000000000..99acaa6ae --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/2.crud/output/src/main.rs @@ -0,0 +1,11 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +mod custom; +mod gen; + +#[tokio::main] +async fn main() -> std::process::ExitCode { + let mut commands = gen::runtime::CustomCommands::new(); + custom::register(&mut commands); + gen::cli::run(custom::overrides(), commands).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/input.json b/packages/xyd-opencli2rust/__fixtures__/3.nested/input.json new file mode 100644 index 000000000..79aba1c74 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/input.json @@ -0,0 +1,196 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "stores-api", + "version": "1.0.0" + }, + "x-openapi": { + "servers": [ + "https://api.stores.test/v2" + ] + }, + "commands": [ + { + "name": "stores", + "commands": [ + { + "name": "items", + "commands": [ + { + "name": "list", + "description": "List items in a store.", + "arguments": [ + { + "name": "store-id", + "required": true + } + ], + "options": [ + { + "name": "limit", + "group": "query", + "arguments": [ + { + "name": "integer" + } + ] + } + ], + "x-openapi": { + "method": "get", + "path": "/stores/{store_id}/items", + "operationId": "listStoreItems", + "params": [ + { + "in": "path", + "name": "store_id", + "from": "argument:store-id", + "required": true + }, + { + "in": "query", + "name": "limit", + "from": "option:limit" + } + ] + } + }, + { + "name": "create", + "description": "Create an item in a store.", + "arguments": [ + { + "name": "store-id", + "required": true + } + ], + "options": [ + { + "name": "sku", + "group": "body", + "required": true, + "arguments": [ + { + "name": "string" + } + ] + }, + { + "name": "quantity", + "group": "body", + "arguments": [ + { + "name": "integer" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/stores/{store_id}/items", + "operationId": "createStoreItem", + "contentType": "application/json", + "params": [ + { + "in": "path", + "name": "store_id", + "from": "argument:store-id", + "required": true + } + ], + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "sku", + "from": "option:sku", + "jsonPath": "sku", + "encoding": "string", + "required": true + }, + { + "name": "quantity", + "from": "option:quantity", + "jsonPath": "quantity", + "encoding": "integer" + } + ] + } + } + }, + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieve a store item.", + "arguments": [ + { + "name": "store-id", + "required": true + }, + { + "name": "item-id", + "required": true + } + ], + "x-openapi": { + "method": "get", + "path": "/stores/{store_id}/items/{item_id}", + "operationId": "retrieveStoreItem", + "params": [ + { + "in": "path", + "name": "store_id", + "from": "argument:store-id", + "required": true + }, + { + "in": "path", + "name": "item_id", + "from": "argument:item-id", + "required": true + } + ] + } + }, + { + "name": "archive", + "description": "Archive a store item.", + "arguments": [ + { + "name": "store-id", + "required": true + }, + { + "name": "item-id", + "required": true + } + ], + "x-openapi": { + "method": "post", + "path": "/stores/{store_id}/items/{item_id}/archive", + "operationId": "archiveStoreItem", + "params": [ + { + "in": "path", + "name": "store_id", + "from": "argument:store-id", + "required": true + }, + { + "in": "path", + "name": "item_id", + "from": "argument:item-id", + "required": true + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/.gitignore b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/.gitignore @@ -0,0 +1 @@ +/target diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/Cargo.toml b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/Cargo.toml new file mode 100644 index 000000000..27dd872da --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/Cargo.toml @@ -0,0 +1,17 @@ +# Generated once by opencli2rust — yours to edit; regeneration never overwrites it. + +[package] +name = "stores_api" +version = "1.0.0" +edition = "2021" + +[dependencies] +clap = { version = "4", features = ["string"] } +percent-encoding = "2" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +serde_json = "1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } + +[[bin]] +name = "stores-api" +path = "src/main.rs" diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/custom/mod.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/custom/mod.rs new file mode 100644 index 000000000..f6944805d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/custom/mod.rs @@ -0,0 +1,29 @@ +//! Yours — scaffolded once by opencli2rust, never overwritten by regeneration. +//! +//! Two extension seams, both optional: +//! * `CliOverrides` on [`Custom`] — hook/override every generated command +//! (see src/gen/runtime/overrides.rs for the available methods). +//! * [`register`] — add fully custom commands anywhere in the tree; a command +//! registered on an existing path takes over that command's behavior. + +use crate::gen::runtime::{CliOverrides, CustomCommands}; + +/// Override only what you need — every trait method has a default. +pub struct Custom; + +impl CliOverrides for Custom {} + +/// Register hand-written commands. Called by the generated `main`. +pub fn register(commands: &mut CustomCommands) { + let _ = commands; + // Example — a fully custom `tools hello` subcommand: + // + // commands.add(&["tools"], clap::Command::new("hello").about("Say hello"), |_ctx, _m| async move { + // println!("hello"); + // Ok(()) + // }); +} + +pub fn overrides() -> Custom { + Custom +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cli.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cli.rs new file mode 100644 index 000000000..e069544a6 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cli.rs @@ -0,0 +1,52 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::ArgMatches; + +use super::cmd; +use super::runtime::{CliOverrides, Context, CustomCommands}; + +/// The generated root command tree. +pub fn root_command() -> clap::Command { + clap::Command::new("stores-api") + .version("1.0.0") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(cmd::stores::command()) +} + +/// Parse args, resolve the invoked command path, and dispatch — custom commands +/// first (so user registrations can override generated behavior), then the +/// generated handlers. +pub async fn run(o: O, customs: CustomCommands) -> ExitCode { + let matches = customs.graft(root_command()).get_matches(); + let ctx = Context::from_env(); + let (cmd_path, leaf) = descend(&matches); + + if let Some(handler) = customs.find(&cmd_path) { + return match handler(ctx.clone(), leaf.clone()).await { + Ok(()) => ExitCode::SUCCESS, + Err(err) => o.print_error(&cmd_path, &err), + }; + } + + match cmd_path.first().map(String::as_str) { + Some("stores") => cmd::stores::run(&ctx, &o, &cmd_path, leaf).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +/// Walk matches to the invoked leaf, collecting the full command path. +fn descend(matches: &ArgMatches) -> (Vec, &ArgMatches) { + let mut path = Vec::new(); + let mut current = matches; + while let Some((name, sub)) = current.subcommand() { + path.push(name.to_string()); + current = sub; + } + (path, current) +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cmd/mod.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cmd/mod.rs new file mode 100644 index 000000000..0d1c6a191 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cmd/mod.rs @@ -0,0 +1,3 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod stores; diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cmd/stores.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cmd/stores.rs new file mode 100644 index 000000000..2665314c3 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/cmd/stores.rs @@ -0,0 +1,177 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("stores") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("items") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("List items in a store.") + .arg( + Arg::new("store-id") + .required(true), + ) + .arg( + Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(i64)), + ), + ) + .subcommand( + Command::new("create") + .about("Create an item in a store.") + .arg( + Arg::new("store-id") + .required(true), + ) + .arg( + Arg::new("sku") + .long("sku") + .required(true), + ) + .arg( + Arg::new("quantity") + .long("quantity") + .value_parser(clap::value_parser!(i64)), + ), + ) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .about("Retrieve a store item.") + .arg( + Arg::new("store-id") + .required(true), + ) + .arg( + Arg::new("item-id") + .required(true), + ), + ) + .subcommand( + Command::new("archive") + .about("Archive a store item.") + .arg( + Arg::new("store-id") + .required(true), + ) + .arg( + Arg::new("item-id") + .required(true), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["stores", "items", "list"] => handle_stores_items_list(ctx, o, cmd_path, m).await, + ["stores", "items", "create"] => handle_stores_items_create(ctx, o, cmd_path, m).await, + ["stores", "items", "retrieve"] => handle_stores_items_retrieve(ctx, o, cmd_path, m).await, + ["stores", "items", "archive"] => handle_stores_items_archive(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_stores_items_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let store_id = m.get_one::("store-id").map(String::as_str).unwrap_or(""); + let path = format!("/stores/{}/items", runtime::path_escape(store_id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("limit") { + query.push(("limit", v.to_string())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_stores_items_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let store_id = m.get_one::("store-id").map(String::as_str).unwrap_or(""); + let path = format!("/stores/{}/items", runtime::path_escape(store_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("sku") { + body.insert("sku".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("quantity") { + body.insert("quantity".to_string(), serde_json::Value::from(*v)); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_stores_items_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let store_id = m.get_one::("store-id").map(String::as_str).unwrap_or(""); + let item_id = m.get_one::("item-id").map(String::as_str).unwrap_or(""); + let path = format!("/stores/{}/items/{}", runtime::path_escape(store_id), runtime::path_escape(item_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_stores_items_archive( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let store_id = m.get_one::("store-id").map(String::as_str).unwrap_or(""); + let item_id = m.get_one::("item-id").map(String::as_str).unwrap_or(""); + let path = format!("/stores/{}/items/{}/archive", runtime::path_escape(store_id), runtime::path_escape(item_id)); + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/mod.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/mod.rs new file mode 100644 index 000000000..3906dd730 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/mod.rs @@ -0,0 +1,5 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod cli; +pub mod cmd; +pub mod runtime; diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/config.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/config.rs new file mode 100644 index 000000000..c6eb20e83 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/config.rs @@ -0,0 +1,18 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Spec-derived configuration: the API base URL and environment-based auth. + +const DEFAULT_BASE_URL: &str = "https://api.stores.test/v2"; + +/// The API base URL, overridable via `STORES_API_BASE_URL`. +pub fn base_url() -> String { + match std::env::var("STORES_API_BASE_URL") { + Ok(v) if !v.is_empty() => v, + _ => DEFAULT_BASE_URL.to_string(), + } +} + +/// Attach credentials read from the environment to the request. +pub fn apply_auth(rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder { + rb +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/custom.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/custom.rs new file mode 100644 index 000000000..693254f72 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/custom.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::future::Future; +use std::pin::Pin; + +use clap::{ArgMatches, Command}; + +use super::http::Error; +use super::Context; + +/// The boxed async handler for one custom command (owned inputs, so plain +/// `|ctx, m| async move { ... }` closures fit without lifetime plumbing). +pub type CustomHandler = Box Pin>>>>; + +struct CustomEntry { + under: Vec, + command: Command, + handler: CustomHandler, +} + +/// Hand-written commands registered from `src/custom/mod.rs`, grafted into the +/// generated tree before parsing and dispatched BEFORE generated handlers — a +/// custom command registered on an existing path overrides its behavior. +#[derive(Default)] +pub struct CustomCommands { + entries: Vec, +} + +impl CustomCommands { + pub fn new() -> Self { + Self::default() + } + + /// Add `command` under a parent path (`&[]` = top level): + /// `add(&["tools"], Command::new("hello"), |ctx, m| async move { ... })` + /// registers ` tools hello`. Missing parents are created. When the full + /// path already exists in the generated tree, the generated clap definition + /// keeps handling parsing/help, but YOUR handler runs instead. + #[allow(dead_code)] // user-facing API, called from src/custom/mod.rs + pub fn add(&mut self, under: &[&str], command: Command, handler: H) + where + H: Fn(Context, ArgMatches) -> F + 'static, + F: Future> + 'static, + { + self.entries.push(CustomEntry { + under: under.iter().map(|s| s.to_string()).collect(), + command, + handler: Box::new(move |ctx, m| Box::pin(handler(ctx, m))), + }); + } + + /// Graft registered commands into the generated clap tree (new paths only — + /// existing definitions are left untouched; dispatch still prefers custom). + pub(crate) fn graft(&self, mut root: Command) -> Command { + for entry in &self.entries { + let mut full = entry.under.clone(); + full.push(entry.command.get_name().to_string()); + if path_exists(&root, &full) { + continue; + } + root = graft_at(root, &entry.under, entry.command.clone()); + } + root + } + + /// The handler registered for exactly this invoked command path, if any. + pub(crate) fn find(&self, path: &[String]) -> Option<&CustomHandler> { + self.entries + .iter() + .find(|e| { + e.under.len() + 1 == path.len() + && e.under.iter().zip(path.iter()).all(|(a, b)| a == b) + && path.last().map(String::as_str) == Some(e.command.get_name()) + }) + .map(|e| &e.handler) + } +} + +fn path_exists(root: &Command, path: &[String]) -> bool { + let mut current = root; + for name in path { + match current.find_subcommand(name) { + Some(sub) => current = sub, + None => return false, + } + } + true +} + +fn graft_at(parent: Command, under: &[String], cmd: Command) -> Command { + match under.split_first() { + None => parent.subcommand(cmd), + Some((head, rest)) => { + let rest = rest.to_vec(); + if parent.find_subcommand(head).is_some() { + parent.mut_subcommand(head.clone(), move |sub| graft_at(sub, &rest, cmd)) + } else { + let node = Command::new(head.clone()).subcommand_required(true).arg_required_else_help(true); + parent.subcommand(graft_at(node, &rest, cmd)) + } + } + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/http.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/http.rs new file mode 100644 index 000000000..9ee976257 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/http.rs @@ -0,0 +1,50 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Static HTTP primitives shared by every command handler. + +use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; +use serde_json::Value; + +/// Escape a value for use inside a URL path segment (strict RFC 3986 unreserved). +const PATH_SEGMENT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'_').remove(b'.').remove(b'~'); + +pub fn path_escape(value: &str) -> String { + utf8_percent_encode(value, PATH_SEGMENT).to_string() +} + +/// A generic API request assembled by a command handler. +pub struct Request { + pub method: &'static str, + pub path: String, + pub query: Vec<(&'static str, String)>, + pub headers: Vec<(&'static str, String)>, + pub body: Option>, +} + +/// A request failure, inspectable from `CliOverrides::recover_error` / `print_error`. +#[derive(Debug)] +pub enum Error { + /// The server answered with a 4xx/5xx status; `body` is the decoded response. + Http { status: u16, body: Value }, + /// The request could not be sent or the response could not be read. + Transport(String), + /// A handler-side failure (flag decoding, body assembly, custom command logic). + Invalid(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Http { status, body } => write!(f, "request failed: {status}: {body}"), + Error::Transport(message) => write!(f, "request error: {message}"), + Error::Invalid(message) => write!(f, "{message}"), + } + } +} + +impl std::error::Error for Error {} + +/// Decode a response body: JSON when it parses, the raw text otherwise. +pub(crate) fn decode(bytes: &[u8]) -> Value { + serde_json::from_slice(bytes).unwrap_or_else(|_| Value::String(String::from_utf8_lossy(bytes).into_owned())) +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/mod.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/mod.rs new file mode 100644 index 000000000..f03c99a28 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/mod.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! The vendored runtime: request execution context, override hooks, and the +//! custom-command registry. Extension points live in `overrides.rs` (the +//! `CliOverrides` trait) and `custom.rs` (the `CustomCommands` registry). + +pub mod config; +mod custom; +mod http; +mod overrides; + +pub use custom::CustomCommands; +pub use http::{path_escape, Error, Request}; +pub use overrides::CliOverrides; + +use std::process::ExitCode; + +use serde_json::Value; + +/// Everything a handler (generated or custom) needs to reach the API: the +/// configured base URL and an HTTP client with the spec's auth applied. +#[derive(Clone)] +pub struct Context { + pub base_url: String, + http: reqwest::Client, +} + +impl Context { + pub fn from_env() -> Self { + Self { + base_url: config::base_url(), + http: reqwest::Client::new(), + } + } + + /// Execute against the configured base URL with the spec's auth applied, + /// decoding the response (JSON when it parses, raw text otherwise). + pub async fn execute(&self, req: Request) -> Result { + let (status, bytes) = self.send(req).await?; + let value = http::decode(&bytes); + if status >= 400 { + return Err(Error::Http { status, body: value }); + } + Ok(value) + } + + /// Raw-bytes variant of [`execute`](Self::execute) for binary/non-JSON endpoints. + #[allow(dead_code)] // user-facing API for custom command handlers + pub async fn execute_raw(&self, req: Request) -> Result, Error> { + let (status, bytes) = self.send(req).await?; + if status >= 400 { + return Err(Error::Http { status, body: http::decode(&bytes) }); + } + Ok(bytes) + } + + async fn send(&self, req: Request) -> Result<(u16, Vec), Error> { + let endpoint = format!("{}{}", self.base_url.trim_end_matches('/'), req.path); + let method = reqwest::Method::from_bytes(req.method.as_bytes()).map_err(|e| Error::Invalid(e.to_string()))?; + let mut rb = self.http.request(method, &endpoint); + if !req.query.is_empty() { + rb = rb.query(&req.query); + } + let has_content_type = req.headers.iter().any(|(k, _)| k.eq_ignore_ascii_case("content-type")); + for (key, value) in &req.headers { + rb = rb.header(*key, value.as_str()); + } + if let Some(body) = req.body { + if !has_content_type { + rb = rb.header("Content-Type", "application/json"); + } + rb = rb.body(body); + } + rb = config::apply_auth(rb); + let resp = rb.send().await.map_err(|e| Error::Transport(e.to_string()))?; + let status = resp.status().as_u16(); + let bytes = resp.bytes().await.map_err(|e| Error::Transport(e.to_string()))?.to_vec(); + Ok((status, bytes)) + } +} + +/// Thread one generated request through the override hooks: mutate, execute, +/// transform, recover, then print. Printing happens ONLY here (via the trait), +/// so overrides fully control the CLI's output. +pub async fn run_request( + ctx: &Context, + o: &O, + cmd_path: &[String], + mut req: Request, +) -> ExitCode { + o.before_request(ctx, cmd_path, &mut req); + let outcome = match ctx.execute(req).await { + Ok(value) => o.transform_response(ctx, cmd_path, value), + Err(err) => Err(err), + }; + match outcome.or_else(|err| o.recover_error(ctx, cmd_path, err)) { + Ok(value) => { + o.print_success(cmd_path, &value); + ExitCode::SUCCESS + } + Err(err) => o.print_error(cmd_path, &err), + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/overrides.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/overrides.rs new file mode 100644 index 000000000..e1af75754 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/gen/runtime/overrides.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use serde_json::Value; + +use super::http::{Error, Request}; +use super::Context; + +/// The override seam for every generated command. Implement this on your type in +/// `src/custom/mod.rs` — every method has a default, so `impl CliOverrides for +/// Custom {}` compiles and behaves exactly like the stock CLI. +pub trait CliOverrides { + /// Mutate the assembled request before it is sent (extra headers, alternate + /// auth, tracing). `cmd_path` is the invoked command path, e.g. `["models", "list"]`. + fn before_request(&self, ctx: &Context, cmd_path: &[String], req: &mut Request) { + let _ = (ctx, cmd_path, req); + } + + /// Transform the decoded response before printing. + fn transform_response(&self, ctx: &Context, cmd_path: &[String], value: Value) -> Result { + let _ = (ctx, cmd_path); + Ok(value) + } + + /// Recover from a failure: return `Ok(value)` to turn it into a success, + /// or hand the error back to keep failing. + fn recover_error(&self, ctx: &Context, cmd_path: &[String], err: Error) -> Result { + let _ = (ctx, cmd_path); + Err(err) + } + + /// Print a successful result. Default: pretty JSON (raw text for non-JSON bodies). + fn print_success(&self, cmd_path: &[String], value: &Value) { + let _ = cmd_path; + match value { + Value::String(text) => println!("{text}"), + other => match serde_json::to_string_pretty(other) { + Ok(text) => println!("{text}"), + Err(_) => println!("{other}"), + }, + } + } + + /// Print a failure and choose the process exit code. + fn print_error(&self, cmd_path: &[String], err: &Error) -> ExitCode { + let _ = cmd_path; + eprintln!("error: {err}"); + ExitCode::FAILURE + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/main.rs b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/main.rs new file mode 100644 index 000000000..99acaa6ae --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/3.nested/output/src/main.rs @@ -0,0 +1,11 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +mod custom; +mod gen; + +#[tokio::main] +async fn main() -> std::process::ExitCode { + let mut commands = gen::runtime::CustomCommands::new(); + custom::register(&mut commands); + gen::cli::run(custom::overrides(), commands).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/input.json b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/input.json new file mode 100644 index 000000000..4d6d8e7df --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/input.json @@ -0,0 +1,153 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "chat-api", + "version": "1.0.0" + }, + "x-openapi": { + "servers": [ + "https://api.chat.test/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "CHAT_API_API_KEY", + "scheme": "bearer" + } + ] + }, + "commands": [ + { + "name": "chat", + "commands": [ + { + "name": "completions", + "commands": [ + { + "name": "create", + "description": "Creates a model response for the given chat conversation.", + "options": [ + { + "name": "model", + "group": "body", + "required": true, + "description": "ID of the model to use.", + "arguments": [ + { + "name": "string", + "acceptedValues": [ + "gpt-4o", + "gpt-4o-mini", + "o3" + ] + } + ] + }, + { + "name": "messages", + "group": "body", + "required": true, + "description": "A list of messages comprising the conversation.", + "arguments": [ + { + "name": "json", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "temperature", + "group": "body", + "description": "Sampling temperature.", + "arguments": [ + { + "name": "number" + } + ] + }, + { + "name": "stop", + "group": "body", + "arguments": [ + { + "name": "string", + "arity": { + "minimum": 0 + } + } + ] + }, + { + "name": "stream", + "group": "body" + }, + { + "name": "response-format", + "group": "body", + "arguments": [ + { + "name": "json" + } + ] + } + ], + "x-openapi": { + "method": "post", + "path": "/chat/completions", + "operationId": "createChatCompletion", + "contentType": "application/json", + "body": { + "style": "flatten", + "contentType": "application/json", + "properties": [ + { + "name": "model", + "from": "option:model", + "jsonPath": "model", + "encoding": "string", + "required": true + }, + { + "name": "messages", + "from": "option:messages", + "jsonPath": "messages", + "encoding": "array", + "required": true + }, + { + "name": "temperature", + "from": "option:temperature", + "jsonPath": "temperature", + "encoding": "number" + }, + { + "name": "stop", + "from": "option:stop", + "jsonPath": "stop", + "encoding": "array" + }, + { + "name": "stream", + "from": "option:stream", + "jsonPath": "stream", + "encoding": "boolean" + }, + { + "name": "response_format", + "from": "option:response-format", + "jsonPath": "response_format", + "encoding": "json" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/.gitignore b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/.gitignore @@ -0,0 +1 @@ +/target diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/Cargo.toml b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/Cargo.toml new file mode 100644 index 000000000..851f5b109 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/Cargo.toml @@ -0,0 +1,17 @@ +# Generated once by opencli2rust — yours to edit; regeneration never overwrites it. + +[package] +name = "chat_api" +version = "1.0.0" +edition = "2021" + +[dependencies] +clap = { version = "4", features = ["string"] } +percent-encoding = "2" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +serde_json = "1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } + +[[bin]] +name = "chat-api" +path = "src/main.rs" diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/custom/mod.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/custom/mod.rs new file mode 100644 index 000000000..f6944805d --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/custom/mod.rs @@ -0,0 +1,29 @@ +//! Yours — scaffolded once by opencli2rust, never overwritten by regeneration. +//! +//! Two extension seams, both optional: +//! * `CliOverrides` on [`Custom`] — hook/override every generated command +//! (see src/gen/runtime/overrides.rs for the available methods). +//! * [`register`] — add fully custom commands anywhere in the tree; a command +//! registered on an existing path takes over that command's behavior. + +use crate::gen::runtime::{CliOverrides, CustomCommands}; + +/// Override only what you need — every trait method has a default. +pub struct Custom; + +impl CliOverrides for Custom {} + +/// Register hand-written commands. Called by the generated `main`. +pub fn register(commands: &mut CustomCommands) { + let _ = commands; + // Example — a fully custom `tools hello` subcommand: + // + // commands.add(&["tools"], clap::Command::new("hello").about("Say hello"), |_ctx, _m| async move { + // println!("hello"); + // Ok(()) + // }); +} + +pub fn overrides() -> Custom { + Custom +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cli.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cli.rs new file mode 100644 index 000000000..c321233c7 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cli.rs @@ -0,0 +1,52 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::ArgMatches; + +use super::cmd; +use super::runtime::{CliOverrides, Context, CustomCommands}; + +/// The generated root command tree. +pub fn root_command() -> clap::Command { + clap::Command::new("chat-api") + .version("1.0.0") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(cmd::chat::command()) +} + +/// Parse args, resolve the invoked command path, and dispatch — custom commands +/// first (so user registrations can override generated behavior), then the +/// generated handlers. +pub async fn run(o: O, customs: CustomCommands) -> ExitCode { + let matches = customs.graft(root_command()).get_matches(); + let ctx = Context::from_env(); + let (cmd_path, leaf) = descend(&matches); + + if let Some(handler) = customs.find(&cmd_path) { + return match handler(ctx.clone(), leaf.clone()).await { + Ok(()) => ExitCode::SUCCESS, + Err(err) => o.print_error(&cmd_path, &err), + }; + } + + match cmd_path.first().map(String::as_str) { + Some("chat") => cmd::chat::run(&ctx, &o, &cmd_path, leaf).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +/// Walk matches to the invoked leaf, collecting the full command path. +fn descend(matches: &ArgMatches) -> (Vec, &ArgMatches) { + let mut path = Vec::new(); + let mut current = matches; + while let Some((name, sub)) = current.subcommand() { + path.push(name.to_string()); + current = sub; + } + (path, current) +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cmd/chat.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cmd/chat.rs new file mode 100644 index 000000000..414bd9669 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cmd/chat.rs @@ -0,0 +1,113 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("chat") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("completions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Creates a model response for the given chat conversation.") + .arg( + Arg::new("model") + .long("model") + .help("ID of the model to use.") + .required(true), + ) + .arg( + Arg::new("messages") + .long("messages") + .action(clap::ArgAction::Append) + .value_delimiter(',') + .help("A list of messages comprising the conversation.") + .required(true), + ) + .arg( + Arg::new("temperature") + .long("temperature") + .value_parser(clap::value_parser!(f64)) + .help("Sampling temperature."), + ) + .arg( + Arg::new("stop") + .long("stop") + .action(clap::ArgAction::Append) + .value_delimiter(','), + ) + .arg( + Arg::new("stream") + .long("stream") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)), + ) + .arg( + Arg::new("response-format") + .long("response-format"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["chat", "completions", "create"] => handle_chat_completions_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_chat_completions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/chat/completions".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("model") { + body.insert("model".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(vs) = m.get_many::("messages") { + body.insert("messages".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("temperature") { + body.insert("temperature".to_string(), serde_json::Value::from(*v)); + } + if let Some(vs) = m.get_many::("stop") { + body.insert("stop".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("stream") { + body.insert("stream".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("response-format") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("response_format".to_string(), value); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cmd/mod.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cmd/mod.rs new file mode 100644 index 000000000..cdde4c5fc --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/cmd/mod.rs @@ -0,0 +1,3 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod chat; diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/mod.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/mod.rs new file mode 100644 index 000000000..3906dd730 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/mod.rs @@ -0,0 +1,5 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod cli; +pub mod cmd; +pub mod runtime; diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/config.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/config.rs new file mode 100644 index 000000000..9167f330a --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/config.rs @@ -0,0 +1,23 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Spec-derived configuration: the API base URL and environment-based auth. + +const DEFAULT_BASE_URL: &str = "https://api.chat.test/v1"; + +/// The API base URL, overridable via `CHAT_API_BASE_URL`. +pub fn base_url() -> String { + match std::env::var("CHAT_API_BASE_URL") { + Ok(v) if !v.is_empty() => v, + _ => DEFAULT_BASE_URL.to_string(), + } +} + +/// Attach credentials read from the environment to the request. +pub fn apply_auth(mut rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder { + if let Ok(v) = std::env::var("CHAT_API_API_KEY") { + if !v.is_empty() { + rb = rb.bearer_auth(v); + } + } + rb +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/custom.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/custom.rs new file mode 100644 index 000000000..693254f72 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/custom.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::future::Future; +use std::pin::Pin; + +use clap::{ArgMatches, Command}; + +use super::http::Error; +use super::Context; + +/// The boxed async handler for one custom command (owned inputs, so plain +/// `|ctx, m| async move { ... }` closures fit without lifetime plumbing). +pub type CustomHandler = Box Pin>>>>; + +struct CustomEntry { + under: Vec, + command: Command, + handler: CustomHandler, +} + +/// Hand-written commands registered from `src/custom/mod.rs`, grafted into the +/// generated tree before parsing and dispatched BEFORE generated handlers — a +/// custom command registered on an existing path overrides its behavior. +#[derive(Default)] +pub struct CustomCommands { + entries: Vec, +} + +impl CustomCommands { + pub fn new() -> Self { + Self::default() + } + + /// Add `command` under a parent path (`&[]` = top level): + /// `add(&["tools"], Command::new("hello"), |ctx, m| async move { ... })` + /// registers ` tools hello`. Missing parents are created. When the full + /// path already exists in the generated tree, the generated clap definition + /// keeps handling parsing/help, but YOUR handler runs instead. + #[allow(dead_code)] // user-facing API, called from src/custom/mod.rs + pub fn add(&mut self, under: &[&str], command: Command, handler: H) + where + H: Fn(Context, ArgMatches) -> F + 'static, + F: Future> + 'static, + { + self.entries.push(CustomEntry { + under: under.iter().map(|s| s.to_string()).collect(), + command, + handler: Box::new(move |ctx, m| Box::pin(handler(ctx, m))), + }); + } + + /// Graft registered commands into the generated clap tree (new paths only — + /// existing definitions are left untouched; dispatch still prefers custom). + pub(crate) fn graft(&self, mut root: Command) -> Command { + for entry in &self.entries { + let mut full = entry.under.clone(); + full.push(entry.command.get_name().to_string()); + if path_exists(&root, &full) { + continue; + } + root = graft_at(root, &entry.under, entry.command.clone()); + } + root + } + + /// The handler registered for exactly this invoked command path, if any. + pub(crate) fn find(&self, path: &[String]) -> Option<&CustomHandler> { + self.entries + .iter() + .find(|e| { + e.under.len() + 1 == path.len() + && e.under.iter().zip(path.iter()).all(|(a, b)| a == b) + && path.last().map(String::as_str) == Some(e.command.get_name()) + }) + .map(|e| &e.handler) + } +} + +fn path_exists(root: &Command, path: &[String]) -> bool { + let mut current = root; + for name in path { + match current.find_subcommand(name) { + Some(sub) => current = sub, + None => return false, + } + } + true +} + +fn graft_at(parent: Command, under: &[String], cmd: Command) -> Command { + match under.split_first() { + None => parent.subcommand(cmd), + Some((head, rest)) => { + let rest = rest.to_vec(); + if parent.find_subcommand(head).is_some() { + parent.mut_subcommand(head.clone(), move |sub| graft_at(sub, &rest, cmd)) + } else { + let node = Command::new(head.clone()).subcommand_required(true).arg_required_else_help(true); + parent.subcommand(graft_at(node, &rest, cmd)) + } + } + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/http.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/http.rs new file mode 100644 index 000000000..9ee976257 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/http.rs @@ -0,0 +1,50 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Static HTTP primitives shared by every command handler. + +use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; +use serde_json::Value; + +/// Escape a value for use inside a URL path segment (strict RFC 3986 unreserved). +const PATH_SEGMENT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'_').remove(b'.').remove(b'~'); + +pub fn path_escape(value: &str) -> String { + utf8_percent_encode(value, PATH_SEGMENT).to_string() +} + +/// A generic API request assembled by a command handler. +pub struct Request { + pub method: &'static str, + pub path: String, + pub query: Vec<(&'static str, String)>, + pub headers: Vec<(&'static str, String)>, + pub body: Option>, +} + +/// A request failure, inspectable from `CliOverrides::recover_error` / `print_error`. +#[derive(Debug)] +pub enum Error { + /// The server answered with a 4xx/5xx status; `body` is the decoded response. + Http { status: u16, body: Value }, + /// The request could not be sent or the response could not be read. + Transport(String), + /// A handler-side failure (flag decoding, body assembly, custom command logic). + Invalid(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Http { status, body } => write!(f, "request failed: {status}: {body}"), + Error::Transport(message) => write!(f, "request error: {message}"), + Error::Invalid(message) => write!(f, "{message}"), + } + } +} + +impl std::error::Error for Error {} + +/// Decode a response body: JSON when it parses, the raw text otherwise. +pub(crate) fn decode(bytes: &[u8]) -> Value { + serde_json::from_slice(bytes).unwrap_or_else(|_| Value::String(String::from_utf8_lossy(bytes).into_owned())) +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/mod.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/mod.rs new file mode 100644 index 000000000..f03c99a28 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/mod.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! The vendored runtime: request execution context, override hooks, and the +//! custom-command registry. Extension points live in `overrides.rs` (the +//! `CliOverrides` trait) and `custom.rs` (the `CustomCommands` registry). + +pub mod config; +mod custom; +mod http; +mod overrides; + +pub use custom::CustomCommands; +pub use http::{path_escape, Error, Request}; +pub use overrides::CliOverrides; + +use std::process::ExitCode; + +use serde_json::Value; + +/// Everything a handler (generated or custom) needs to reach the API: the +/// configured base URL and an HTTP client with the spec's auth applied. +#[derive(Clone)] +pub struct Context { + pub base_url: String, + http: reqwest::Client, +} + +impl Context { + pub fn from_env() -> Self { + Self { + base_url: config::base_url(), + http: reqwest::Client::new(), + } + } + + /// Execute against the configured base URL with the spec's auth applied, + /// decoding the response (JSON when it parses, raw text otherwise). + pub async fn execute(&self, req: Request) -> Result { + let (status, bytes) = self.send(req).await?; + let value = http::decode(&bytes); + if status >= 400 { + return Err(Error::Http { status, body: value }); + } + Ok(value) + } + + /// Raw-bytes variant of [`execute`](Self::execute) for binary/non-JSON endpoints. + #[allow(dead_code)] // user-facing API for custom command handlers + pub async fn execute_raw(&self, req: Request) -> Result, Error> { + let (status, bytes) = self.send(req).await?; + if status >= 400 { + return Err(Error::Http { status, body: http::decode(&bytes) }); + } + Ok(bytes) + } + + async fn send(&self, req: Request) -> Result<(u16, Vec), Error> { + let endpoint = format!("{}{}", self.base_url.trim_end_matches('/'), req.path); + let method = reqwest::Method::from_bytes(req.method.as_bytes()).map_err(|e| Error::Invalid(e.to_string()))?; + let mut rb = self.http.request(method, &endpoint); + if !req.query.is_empty() { + rb = rb.query(&req.query); + } + let has_content_type = req.headers.iter().any(|(k, _)| k.eq_ignore_ascii_case("content-type")); + for (key, value) in &req.headers { + rb = rb.header(*key, value.as_str()); + } + if let Some(body) = req.body { + if !has_content_type { + rb = rb.header("Content-Type", "application/json"); + } + rb = rb.body(body); + } + rb = config::apply_auth(rb); + let resp = rb.send().await.map_err(|e| Error::Transport(e.to_string()))?; + let status = resp.status().as_u16(); + let bytes = resp.bytes().await.map_err(|e| Error::Transport(e.to_string()))?.to_vec(); + Ok((status, bytes)) + } +} + +/// Thread one generated request through the override hooks: mutate, execute, +/// transform, recover, then print. Printing happens ONLY here (via the trait), +/// so overrides fully control the CLI's output. +pub async fn run_request( + ctx: &Context, + o: &O, + cmd_path: &[String], + mut req: Request, +) -> ExitCode { + o.before_request(ctx, cmd_path, &mut req); + let outcome = match ctx.execute(req).await { + Ok(value) => o.transform_response(ctx, cmd_path, value), + Err(err) => Err(err), + }; + match outcome.or_else(|err| o.recover_error(ctx, cmd_path, err)) { + Ok(value) => { + o.print_success(cmd_path, &value); + ExitCode::SUCCESS + } + Err(err) => o.print_error(cmd_path, &err), + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/overrides.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/overrides.rs new file mode 100644 index 000000000..e1af75754 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/gen/runtime/overrides.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use serde_json::Value; + +use super::http::{Error, Request}; +use super::Context; + +/// The override seam for every generated command. Implement this on your type in +/// `src/custom/mod.rs` — every method has a default, so `impl CliOverrides for +/// Custom {}` compiles and behaves exactly like the stock CLI. +pub trait CliOverrides { + /// Mutate the assembled request before it is sent (extra headers, alternate + /// auth, tracing). `cmd_path` is the invoked command path, e.g. `["models", "list"]`. + fn before_request(&self, ctx: &Context, cmd_path: &[String], req: &mut Request) { + let _ = (ctx, cmd_path, req); + } + + /// Transform the decoded response before printing. + fn transform_response(&self, ctx: &Context, cmd_path: &[String], value: Value) -> Result { + let _ = (ctx, cmd_path); + Ok(value) + } + + /// Recover from a failure: return `Ok(value)` to turn it into a success, + /// or hand the error back to keep failing. + fn recover_error(&self, ctx: &Context, cmd_path: &[String], err: Error) -> Result { + let _ = (ctx, cmd_path); + Err(err) + } + + /// Print a successful result. Default: pretty JSON (raw text for non-JSON bodies). + fn print_success(&self, cmd_path: &[String], value: &Value) { + let _ = cmd_path; + match value { + Value::String(text) => println!("{text}"), + other => match serde_json::to_string_pretty(other) { + Ok(text) => println!("{text}"), + Err(_) => println!("{other}"), + }, + } + } + + /// Print a failure and choose the process exit code. + fn print_error(&self, cmd_path: &[String], err: &Error) -> ExitCode { + let _ = cmd_path; + eprintln!("error: {err}"); + ExitCode::FAILURE + } +} diff --git a/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/main.rs b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/main.rs new file mode 100644 index 000000000..99acaa6ae --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/4.body-flatten/output/src/main.rs @@ -0,0 +1,11 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +mod custom; +mod gen; + +#[tokio::main] +async fn main() -> std::process::ExitCode { + let mut commands = gen::runtime::CustomCommands::new(); + custom::register(&mut commands); + gen::cli::run(custom::overrides(), commands).await +} diff --git a/packages/xyd-opencli2rust/__fixtures__/5.custom-scaffold/custom.rs b/packages/xyd-opencli2rust/__fixtures__/5.custom-scaffold/custom.rs new file mode 100644 index 000000000..c6bd71114 --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/5.custom-scaffold/custom.rs @@ -0,0 +1,33 @@ +//! Hand-edited custom module simulating a user's customizations. + +use crate::gen::runtime::{CliOverrides, CustomCommands}; + +pub struct Custom; + +impl CliOverrides for Custom { + fn print_success(&self, cmd_path: &[String], value: &serde_json::Value) { + println!("[custom:{}]", cmd_path.join(" ")); + match serde_json::to_string_pretty(value) { + Ok(text) => println!("{text}"), + Err(_) => println!("{value}"), + } + } +} + +pub fn register(commands: &mut CustomCommands) { + // A brand-new command grafted under a new path. + commands.add(&["tools"], clap::Command::new("hello").about("Say hello"), |ctx, _m| async move { + println!("hello from custom code! base_url={}", ctx.base_url); + Ok(()) + }); + + // Override an EXISTING generated command's behavior. + commands.add(&["models"], clap::Command::new("list"), |_ctx, _m| async move { + println!("overridden models list!"); + Ok(()) + }); +} + +pub fn overrides() -> Custom { + Custom +} diff --git a/packages/xyd-opencli2rust/__fixtures__/5.custom-scaffold/input.json b/packages/xyd-opencli2rust/__fixtures__/5.custom-scaffold/input.json new file mode 100644 index 000000000..3af78c5fe --- /dev/null +++ b/packages/xyd-opencli2rust/__fixtures__/5.custom-scaffold/input.json @@ -0,0 +1,74 @@ +{ + "opencli": "1.0.0", + "info": { + "title": "sample-api", + "version": "1.2.0", + "description": "A tiny sample API for testing openapi2opencli.", + "contact": { + "name": "API Support", + "url": "https://example.com/support", + "email": "support@example.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + } + }, + "x-openapi": { + "servers": [ + "https://api.example.com/v1" + ], + "security": [ + { + "type": "http", + "kind": "bearer", + "envVar": "SAMPLE_API_API_KEY", + "scheme": "bearer", + "bearerFormat": "JWT" + } + ] + }, + "commands": [ + { + "name": "models", + "commands": [ + { + "name": "list", + "description": "Lists the currently available models.", + "x-openapi": { + "method": "get", + "path": "/models", + "operationId": "listModels" + } + }, + { + "name": "retrieve", + "aliases": [ + "get" + ], + "description": "Retrieves a model instance.", + "arguments": [ + { + "name": "model", + "required": true, + "description": "The ID of the model to use for this request." + } + ], + "x-openapi": { + "method": "get", + "path": "/models/{model}", + "operationId": "retrieveModel", + "params": [ + { + "in": "path", + "name": "model", + "from": "argument:model", + "required": true + } + ] + } + } + ] + } + ] +} diff --git a/packages/xyd-opencli2rust/__tests__/custom-scaffold.test.ts b/packages/xyd-opencli2rust/__tests__/custom-scaffold.test.ts new file mode 100644 index 000000000..545758d35 --- /dev/null +++ b/packages/xyd-opencli2rust/__tests__/custom-scaffold.test.ts @@ -0,0 +1,80 @@ +// The override-story acceptance tests: user-owned files survive regeneration +// (skipIfExists), and — with `{ merge: true }` — hand-edits to GENERATED files +// survive via the framework's 3-way merge. + +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { opencli2rust, writeProject } from '../index'; +import { CARGO_SMOKE } from './utils'; + +const FIXTURE = path.join(__dirname, '../__fixtures__/5.custom-scaffold'); +const spec = () => JSON.parse(fs.readFileSync(path.join(FIXTURE, 'input.json'), 'utf8')); +const customized = () => fs.readFileSync(path.join(FIXTURE, 'custom.rs'), 'utf8'); + +describe('custom code survives regeneration', () => { + it('skipIfExists preserves user-owned files across regen', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'o2r-custom-')); + try { + await writeProject(opencli2rust(spec()), dir); + + // The user customizes their scaffold + manifest. + fs.writeFileSync(path.join(dir, 'src/custom/mod.rs'), customized()); + const cargo = `${fs.readFileSync(path.join(dir, 'Cargo.toml'), 'utf8')}\n[profile.release]\nlto = "thin"\n`; + fs.writeFileSync(path.join(dir, 'Cargo.toml'), cargo); + + const result = await writeProject(opencli2rust(spec()), dir); + + expect(fs.readFileSync(path.join(dir, 'src/custom/mod.rs'), 'utf8')).toEqual(customized()); + expect(fs.readFileSync(path.join(dir, 'Cargo.toml'), 'utf8')).toEqual(cargo); + expect(result.skipped).toContain('src/custom/mod.rs'); + expect(result.skipped).toContain('Cargo.toml'); + expect(fs.existsSync(path.join(dir, '.sdk/sdk.lock'))).toBe(true); + + if (CARGO_SMOKE) { + // The CUSTOMIZED scaffold must compile against the regenerated code. + execSync('cargo check --quiet', { + cwd: dir, + stdio: 'pipe', + env: { ...process.env, CARGO_TARGET_DIR: process.env.CARGO_TARGET_DIR || path.join(os.tmpdir(), 'o2r-target') }, + }); + } + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it('merge mode preserves hand-edits to generated files', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'o2r-merge-')); + try { + await writeProject(opencli2rust(spec()), dir, { merge: true }); + + const cliPath = path.join(dir, 'src/gen/cli.rs'); + const HAND_EDIT = '// hand-edit that must survive regeneration\n'; + fs.writeFileSync(cliPath, `${fs.readFileSync(cliPath, 'utf8')}\n${HAND_EDIT}`); + + // Same spec: the generation didn't change, so the edited file is left alone. + const noop = await writeProject(opencli2rust(spec()), dir, { merge: true }); + expect(noop.unchanged).toContain('src/gen/cli.rs'); + expect(fs.readFileSync(cliPath, 'utf8')).toContain(HAND_EDIT); + + // Changed spec (new root summary → new .about line in cli.rs): the 3-way + // merge lands BOTH the generator's change and the user's hand-edit. + const changed = spec(); + changed.info = { ...changed.info, summary: 'An updated summary from a regen' }; + const result = await writeProject(opencli2rust(changed), dir, { merge: true }); + + const merged = fs.readFileSync(cliPath, 'utf8'); + expect(merged).toContain('An updated summary from a regen'); + expect(merged).toContain(HAND_EDIT); + expect(result.merged).toContain('src/gen/cli.rs'); + expect(result.mergeConflicts).toEqual([]); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/packages/xyd-opencli2rust/__tests__/docs.test.ts b/packages/xyd-opencli2rust/__tests__/docs.test.ts new file mode 100644 index 000000000..094724657 --- /dev/null +++ b/packages/xyd-opencli2rust/__tests__/docs.test.ts @@ -0,0 +1,87 @@ +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { opencli2rust } from '../index'; +import { CARGO_SMOKE, flattenFiles, writeTree } from './utils'; + +const O2R_FIX = path.join(__dirname, '../__fixtures__/-2.complex.openai'); +// The Go package owns fixture generation (from the vendored OpenAPI oracle) and +// request recording (E2E_RECORD); this package keeps independent COPIES of the +// language-neutral input.json/recorded.json, re-synced by O2R_BUILD_DOCS=1. +const O2G_FIX = path.join(__dirname, '../../xyd-opencli2go/__fixtures__/-2.complex.openai'); + +const BUILD = process.env.O2R_BUILD_DOCS === '1'; + +const cmdFileKey = (files: Record) => + Object.keys(files).find((k) => k.startsWith('src/gen/cmd/') && k !== 'src/gen/cmd/mod.rs'); + +// ---- Generator (opt-in): sync per-method OpenCLI inputs + Rust goldens ---- +describe.runIf(BUILD)('sync opencli fixtures from opencli2go → rust goldens (assumed-correct, for review)', () => { + it('build __fixtures__/-2.complex.openai//{input.json, recorded.json, output.rs}', () => { + let n = 0; + for (const dir of fs.readdirSync(O2G_FIX)) { + const src = path.join(O2G_FIX, dir); + if (!fs.existsSync(path.join(src, 'input.json'))) continue; + const opencli = JSON.parse(fs.readFileSync(path.join(src, 'input.json'), 'utf8')); + const files = flattenFiles(opencli2rust(opencli)); + const key = cmdFileKey(files); + if (!key) continue; + + const out = path.join(O2R_FIX, dir); + fs.mkdirSync(out, { recursive: true }); + fs.copyFileSync(path.join(src, 'input.json'), path.join(out, 'input.json')); + const recorded = path.join(src, 'recorded.json'); + if (fs.existsSync(recorded)) fs.copyFileSync(recorded, path.join(out, 'recorded.json')); + fs.writeFileSync(path.join(out, 'output.rs'), files[key]); + n++; + } + expect(n).toBeGreaterThan(100); + }, 300000); +}); + +// ---- Regen guard (offline; pure opencli → rust) --------------------------- +const fixtures = fs.existsSync(O2R_FIX) + ? fs.readdirSync(O2R_FIX).filter((d) => fs.existsSync(path.join(O2R_FIX, d, 'input.json'))) + : []; + +describe.skipIf(!fixtures.length || BUILD)('opencli2rust docs (opencli → rust, regen guard)', () => { + for (const dir of fixtures) { + it(dir, () => { + const opencli = JSON.parse(fs.readFileSync(path.join(O2R_FIX, dir, 'input.json'), 'utf8')); + const files = flattenFiles(opencli2rust(opencli)); + const key = cmdFileKey(files); + expect(key, `no src/gen/cmd file generated for ${dir}`).toBeTruthy(); + const expected = fs.readFileSync(path.join(O2R_FIX, dir, 'output.rs'), 'utf8'); + expect(files[key as string]).toEqual(expected); + }); + } +}); + +// ---- Optional: a sample of the generated projects compiles ---------------- +describe.runIf(CARGO_SMOKE && fixtures.length > 0)('opencli2rust docs (cargo check smoke, sample)', () => { + // Spread the sample across resources (alphabetical), not just the first few. + const step = Math.max(1, Math.floor(fixtures.length / 8)); + const sample = fixtures.filter((_, i) => i % step === 0).slice(0, 8); + const targetDir = process.env.CARGO_TARGET_DIR || path.join(os.tmpdir(), 'o2r-target'); + for (const dir of sample) { + it(`checks ${dir}`, () => { + const opencli = JSON.parse(fs.readFileSync(path.join(O2R_FIX, dir, 'input.json'), 'utf8')); + const files = flattenFiles(opencli2rust(opencli)); + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'o2r-docs-')); + try { + writeTree(tmp, files); + execSync('cargo check --quiet', { + cwd: tmp, + stdio: 'pipe', + env: { ...process.env, CARGO_TARGET_DIR: targetDir }, + }); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }, 120000); + } +}); diff --git a/packages/xyd-opencli2rust/__tests__/e2e/harness.ts b/packages/xyd-opencli2rust/__tests__/e2e/harness.ts new file mode 100644 index 000000000..6914c221b --- /dev/null +++ b/packages/xyd-opencli2rust/__tests__/e2e/harness.ts @@ -0,0 +1,289 @@ +import { execSync, spawn } from 'node:child_process'; +import http from 'node:http'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +import type { Command, OpencliSpecJson } from '@xyd-js/opencli'; + +import { opencli2rust, writeProject } from '../../index'; +import { buildLeafModel, type FlagType } from '../../src/model'; +import { slug } from '../../src/naming'; + +// Reusable, self-contained end-to-end harness for opencli2rust, mirroring the +// opencli2go harness. An API's whole e2e is one call (see e2e/openai.test.ts): +// defineE2E({ name, cliName, fixturesDir }) // offline binding guard + real-CLI check +// The only inputs are the committed per-method fixtures (input.json = OpenCLI, +// recorded.json = expected request); recording itself is owned by the Go +// package (E2E_RECORD there) — the fixtures here are synced copies. + +export interface RecordedRequest { + method: string; + path: string; + query: string[]; + contentType?: string; + bodyFields: string[]; + auth: string; +} + +export interface RecordedFixture { + args: string[]; // full CLI invocation (command path + positionals + required flags) + request: RecordedRequest; // the request a correct CLI should send +} + +export interface ApiConfig { + name: string; + cliName: string; + /** Per-method fixtures dir: /input.json (OpenCLI) [+ output.rs, recorded.json]. */ + fixturesDir: string; +} + +const PLACEHOLDER = 'EXAMPLE'; + +export function hasCargo(): boolean { + try { + execSync('cargo --version', { stdio: 'ignore' }); + return true; + } catch { + return false; + } +} + +const fromOption = (from: string | undefined): string | undefined => + from?.startsWith('option:') ? from.slice('option:'.length) : undefined; + +/** Walk an OpenCLI spec down its first branch to the single leaf command. */ +export function leafCommand(spec: OpencliSpecJson): { command: Command; path: string[] } | null { + let node = spec.commands?.[0]; + const cp: string[] = []; + while (node) { + cp.push(node.name); + if (node.commands?.length) node = node.commands[0]; + else return { command: node, path: cp }; + } + return null; +} + +/** The CLI invocation + request a correct CLI should record, using required flags. */ +// A typed flag (int/float) rejects a non-numeric placeholder at clap's parse +// stage — so the value must match the emitted flag type. Reuse the generator's +// own type model so the placeholder never drifts from the generated flag. +function examplePlaceholder(flagType: FlagType | undefined): string { + return flagType === 'int' || flagType === 'float' ? '1' : PLACEHOLDER; +} + +function exampleInvocation(leaf: { command: Command; path: string[] }): { args: string[]; setFlags: Set } { + const typeByName = new Map(buildLeafModel(leaf.command).flags.map((f) => [f.flagName, f.flagType])); + const positionals = (leaf.command.arguments || []).map(() => PLACEHOLDER); + const flagArgs: string[] = []; + const setFlags = new Set(); + for (const o of leaf.command.options || []) { + if (!o.required) continue; + setFlags.add(o.name); + flagArgs.push(`--${o.name}`); + if (o.arguments?.length) flagArgs.push(examplePlaceholder(typeByName.get(o.name))); + } + return { args: [...leaf.path, ...positionals, ...flagArgs], setFlags }; +} + +export function expectedRequest(spec: OpencliSpecJson, command: Command, setFlags: Set): RecordedRequest { + const x = (command['x-openapi'] || {}) as any; + const query: string[] = []; + const bodyFields: string[] = []; + for (const p of x.params || []) { + if (p.in === 'query') { + const opt = fromOption(p.from); + if (opt && setFlags.has(opt)) query.push(p.name); + } + } + for (const bp of x.body?.properties || []) { + const opt = fromOption(bp.from); + if (opt && setFlags.has(opt)) bodyFields.push(bp.name); + } + const kind = spec['x-openapi']?.security?.[0]?.kind || x.security?.[0]?.kind || ''; + const req: RecordedRequest = { + method: String(x.method || 'get').toLowerCase(), + path: x.path || '', + query: query.sort(), + bodyFields: bodyFields.sort(), + auth: kind.startsWith('apiKey') ? 'apikey' : kind || 'bearer', + }; + // The generated runtime always assembles a JSON body (multipart is a known gap). + if (x.body) req.contentType = 'application/json'; + return req; +} + +/** Merge per-method OpenCLI docs (single branches) into one full OpenCLI doc. */ +function mergeCommands(target: Command[], source: Command[]) { + for (const s of source) { + const existing = target.find((t) => t.name === s.name); + if (!existing) { + target.push(JSON.parse(JSON.stringify(s))); + } else if (existing.commands?.length && s.commands?.length) { + mergeCommands(existing.commands, s.commands); + } // leaf collision → keep the first (distinct operations shouldn't collide) + } +} + +function loadPerMethod(fixturesDir: string): { slug: string; opencli: OpencliSpecJson; leaf: { command: Command; path: string[] } }[] { + if (!fs.existsSync(fixturesDir)) return []; + const out: { slug: string; opencli: OpencliSpecJson; leaf: { command: Command; path: string[] } }[] = []; + for (const slugDir of fs.readdirSync(fixturesDir).sort()) { + const inPath = path.join(fixturesDir, slugDir, 'input.json'); + if (!fs.existsSync(inPath)) continue; + const opencli = JSON.parse(fs.readFileSync(inPath, 'utf8')) as OpencliSpecJson; + const leaf = leafCommand(opencli); + if (leaf?.command['x-openapi']) out.push({ slug: slugDir, opencli, leaf }); + } + return out; +} + +/** Assemble the full OpenCLI doc by merging the committed per-method docs. */ +export function fullOpencli(fixturesDir: string, cliName: string): OpencliSpecJson { + const methods = loadPerMethod(fixturesDir); + const root: OpencliSpecJson = { + opencli: methods[0]?.opencli.opencli || '1.0.0', + info: methods[0]?.opencli.info || { title: cliName, version: '1.0.0' }, + commands: [], + }; + if (methods[0]?.opencli['x-openapi']) root['x-openapi'] = methods[0].opencli['x-openapi']; + for (const m of methods) mergeCommands(root.commands!, m.opencli.commands || []); + return root; +} + +export class RecordingServer { + private server: http.Server; + port = 0; + last: { method: string; url: string; headers: http.IncomingHttpHeaders; body: string } | null = null; + + constructor() { + this.server = http.createServer((req, res) => { + let body = ''; + req.on('data', (c) => (body += c)); + req.on('end', () => { + this.last = { method: req.method || '', url: req.url || '', headers: req.headers, body }; + res.writeHead(200, { 'content-type': 'application/json' }); + res.end('{"ok":true}'); + }); + }); + } + + start(): Promise { + return new Promise((r) => this.server.listen(0, '127.0.0.1', () => { this.port = (this.server.address() as any).port; r(); })); + } + + stop() { + this.server.close(); + } +} + +export function normalizeRecorded(raw: NonNullable): RecordedRequest { + const [p, qs] = (raw.url || '').split('?'); + const query = qs ? [...new URLSearchParams(qs).keys()].sort() : []; + let bodyFields: string[] = []; + try { + const parsed = raw.body ? JSON.parse(raw.body) : {}; + if (parsed && typeof parsed === 'object') bodyFields = Object.keys(parsed).sort(); + } catch { + /* non-JSON body */ + } + const authHeader = (raw.headers.authorization as string) || ''; + const auth = authHeader.startsWith('Bearer ') ? 'bearer' : authHeader ? 'apikey' : ''; + return { method: (raw.method || '').toLowerCase(), path: p, query, bodyFields, auth, contentType: ((raw.headers['content-type'] as string) || '').split(';')[0] || undefined }; +} + +export function diffRequest(actual: RecordedRequest, fixture: RecordedRequest): string[] { + const errs: string[] = []; + if (actual.method !== fixture.method) errs.push(`method ${actual.method} != ${fixture.method}`); + const re = new RegExp(`^${fixture.path.replace(/[.]/g, '\\.').replace(/\{[^}]+\}/g, '[^/]+')}$`); + if (!re.test(actual.path)) errs.push(`path ${actual.path} !~ ${fixture.path}`); + if (actual.query.join(',') !== fixture.query.join(',')) errs.push(`query [${actual.query}] != [${fixture.query}]`); + for (const f of fixture.bodyFields) if (!actual.bodyFields.includes(f)) errs.push(`body missing ${f}`); + if (actual.auth !== fixture.auth) errs.push(`auth ${actual.auth} != ${fixture.auth}`); + return errs; +} + +async function buildCli(spec: OpencliSpecJson): Promise<{ binPath: string; tmpDir: string }> { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'o2r-e2e-')); + await writeProject(opencli2rust(spec), tmpDir); + const targetDir = process.env.CARGO_TARGET_DIR || path.join(tmpDir, 'target'); + execSync('cargo build --quiet', { cwd: tmpDir, stdio: 'pipe', env: { ...process.env, CARGO_TARGET_DIR: targetDir } }); + const binName = slug(spec.info?.title || 'cli') || 'cli'; + return { binPath: path.join(targetDir, 'debug', binName), tmpDir }; +} + +function runAndRecord(binPath: string, args: string[], server: RecordingServer, env: NodeJS.ProcessEnv): Promise { + return new Promise((resolve) => { + server.last = null; + const p = spawn(binPath, args, { stdio: ['ignore', 'ignore', 'ignore'], env }); + const done = () => resolve(server.last ? normalizeRecorded(server.last) : null); + const t = setTimeout(() => { p.kill('SIGKILL'); done(); }, 8000); + p.on('exit', () => { clearTimeout(t); done(); }); + p.on('error', () => { clearTimeout(t); done(); }); + }); +} + +/** Register the e2e tests for an API from its committed per-method fixtures. */ +export function defineE2E(cfg: ApiConfig) { + const methods = loadPerMethod(cfg.fixturesDir).filter((m) => fs.existsSync(path.join(cfg.fixturesDir, m.slug, 'recorded.json'))); + const items = methods.map((m) => ({ ...m, fixture: JSON.parse(fs.readFileSync(path.join(cfg.fixturesDir, m.slug, 'recorded.json'), 'utf8')) as RecordedFixture })); + const E2E = process.env.E2E_CLI === '1'; + const ENV = (port: number) => ({ ...process.env, [`${cfg.cliName.toUpperCase()}_BASE_URL`]: `http://127.0.0.1:${port}`, [`${cfg.cliName.toUpperCase()}_API_KEY`]: 'sk-e2e-test' }); + + // Offline guard (no Rust toolchain): fixtures stay consistent with the OpenCLI binding. + describe.skipIf(!items.length)(`${cfg.name} e2e: recorded requests match the OpenCLI binding`, () => { + for (const m of items) { + it(`${m.leaf.path.join(' ')} → ${m.fixture.request.method.toUpperCase()} ${m.fixture.request.path}`, () => { + const { setFlags } = exampleInvocation(m.leaf); + expect(expectedRequest(m.opencli, m.leaf.command, setFlags)).toEqual(m.fixture.request); + }); + } + }); + + // Real-CLI check (gated; runs where cargo is available, e.g. CI). + if (E2E && items.length) { + describe(`${cfg.name} e2e: real CLI requests match fixtures`, () => { + const server = new RecordingServer(); + let binPath = ''; + let tmpDir = ''; + let buildError: unknown = null; + let binaryRuns = false; + + beforeAll(async () => { + await server.start(); + if (!hasCargo()) { + buildError = new Error('cargo toolchain not available'); + return; + } + try { + ({ binPath, tmpDir } = await buildCli(fullOpencli(cfg.fixturesDir, cfg.cliName))); + } catch (e) { + buildError = e; + return; + } + binaryRuns = (await runAndRecord(binPath, items[0].fixture.args, server, ENV(server.port))) !== null; + }, 600000); + + afterAll(() => { + server.stop(); + if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it('the whole CLI compiled', () => { + expect(buildError, `${buildError}`).toBeNull(); + expect(fs.existsSync(binPath)).toBe(true); + }); + + for (const m of items) { + it(`${m.leaf.path.join(' ')} → ${m.fixture.request.method.toUpperCase()} ${m.fixture.request.path}`, async (ctx) => { + if (!binaryRuns) return ctx.skip(); + const actual = await runAndRecord(binPath, m.fixture.args, server, ENV(server.port)); + expect(actual, `${m.slug} made no request`).not.toBeNull(); + expect(diffRequest(actual!, m.fixture.request), m.slug).toEqual([]); + }, 15000); + } + }); + } +} diff --git a/packages/xyd-opencli2rust/__tests__/e2e/openai.test.ts b/packages/xyd-opencli2rust/__tests__/e2e/openai.test.ts new file mode 100644 index 000000000..fdbcd668f --- /dev/null +++ b/packages/xyd-opencli2rust/__tests__/e2e/openai.test.ts @@ -0,0 +1,10 @@ +import path from 'node:path'; + +import { defineE2E } from './harness'; + +// Fixtures are synced copies from xyd-opencli2go (O2R_BUILD_DOCS=1 re-syncs). +defineE2E({ + name: 'openai', + cliName: 'openai', + fixturesDir: path.join(__dirname, '../../__fixtures__/-2.complex.openai'), +}); diff --git a/packages/xyd-opencli2rust/__tests__/opencli2rust.test.ts b/packages/xyd-opencli2rust/__tests__/opencli2rust.test.ts new file mode 100644 index 000000000..e5069f93c --- /dev/null +++ b/packages/xyd-opencli2rust/__tests__/opencli2rust.test.ts @@ -0,0 +1,21 @@ +import { describe, it } from 'vitest'; + +import { CARGO_SMOKE, cargoCheckSmoke, testFixture } from './utils'; + +const FIXTURES = ['1.basic', '2.crud', '3.nested', '4.body-flatten']; + +describe('opencli2rust golden fixtures', () => { + for (const name of FIXTURES) { + it(name, () => { + testFixture(name); + }); + } +}); + +describe.runIf(CARGO_SMOKE)('opencli2rust cargo smoke', () => { + for (const name of FIXTURES) { + it(`${name} cargo check`, () => { + cargoCheckSmoke(name); + }); + } +}); diff --git a/packages/xyd-opencli2rust/__tests__/utils.ts b/packages/xyd-opencli2rust/__tests__/utils.ts new file mode 100644 index 000000000..290107f65 --- /dev/null +++ b/packages/xyd-opencli2rust/__tests__/utils.ts @@ -0,0 +1,96 @@ +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { expect } from 'vitest'; + +import { opencli2rust } from '../index'; +import type { Opencli2RustOptions } from '../index'; + +// REGEN=1 regenerates the golden output/ trees instead of asserting. +const REGENERATE = process.env.REGEN === '1'; + +// O2R_CARGO_SMOKE=1 (and a Rust toolchain + network) enables the cargo check smoke. +export const CARGO_SMOKE = process.env.O2R_CARGO_SMOKE === '1' && hasCargo(); + +export function hasCargo(): boolean { + try { + execSync('cargo --version', { stdio: 'ignore' }); + return true; + } catch { + return false; + } +} + +function fixturePath(name: string): string { + return path.join(__dirname, '../__fixtures__', name); +} + +function readSpec(name: string) { + return JSON.parse(fs.readFileSync(path.join(fixturePath(name), 'input.json'), 'utf8')); +} + +/** Flatten a ProjectFileMap to plain contents (goldens don't carry writeModes). */ +export function flattenFiles(files: ReturnType): Record { + const out: Record = {}; + for (const [rel, value] of Object.entries(files)) { + out[rel] = typeof value === 'string' ? value : value.content; + } + return out; +} + +export function fileContents(name: string, options?: Opencli2RustOptions): Record { + return flattenFiles(opencli2rust(readSpec(name), options)); +} + +export function listFiles(dir: string, base = dir): Record { + const out: Record = {}; + if (!fs.existsSync(dir)) return out; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) Object.assign(out, listFiles(full, base)); + else out[path.relative(base, full).split(path.sep).join('/')] = fs.readFileSync(full, 'utf8'); + } + return out; +} + +export function writeTree(dir: string, files: Record) { + fs.rmSync(dir, { recursive: true, force: true }); + for (const [rel, content] of Object.entries(files)) { + const full = path.join(dir, rel); + fs.mkdirSync(path.dirname(full), { recursive: true }); + fs.writeFileSync(full, content); + } +} + +/** Golden test: opencli2rust(input.json) === the committed output/ file tree. */ +export function testFixture(name: string, options?: Opencli2RustOptions) { + const files = fileContents(name, options); + const outDir = path.join(fixturePath(name), 'output'); + + if (REGENERATE) writeTree(outDir, files); + + const expected = listFiles(outDir); + expect(Object.keys(files).sort()).toEqual(Object.keys(expected).sort()); + for (const [rel, content] of Object.entries(files)) { + expect(content, `mismatch in ${name}/${rel}`).toEqual(expected[rel]); + } +} + +/** Optional: write the project to a temp dir and run `cargo check` (shared CARGO_TARGET_DIR). */ +export function cargoCheckSmoke(name: string, options?: Opencli2RustOptions) { + const files = fileContents(name, options); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), `o2r-${name.replace(/\W/g, '')}-`)); + const targetDir = process.env.CARGO_TARGET_DIR || path.join(os.tmpdir(), 'o2r-target'); + try { + writeTree(dir, files); + execSync('cargo check --quiet', { + cwd: dir, + stdio: 'pipe', + env: { ...process.env, CARGO_TARGET_DIR: targetDir }, + }); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +} diff --git a/packages/xyd-opencli2rust/biome.json b/packages/xyd-opencli2rust/biome.json new file mode 100644 index 000000000..214d37e27 --- /dev/null +++ b/packages/xyd-opencli2rust/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", + "files": { + "ignore": ["dist/**", "node_modules/**", "__fixtures__/**"] + }, + "organizeImports": { "enabled": true }, + "formatter": { + "enabled": true, + "indentWidth": 2, + "indentStyle": "space", + "lineWidth": 120 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded" + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} diff --git a/packages/xyd-opencli2rust/index.ts b/packages/xyd-opencli2rust/index.ts new file mode 100644 index 000000000..8420b1093 --- /dev/null +++ b/packages/xyd-opencli2rust/index.ts @@ -0,0 +1 @@ +export * from './src'; diff --git a/packages/xyd-opencli2rust/package.json b/packages/xyd-opencli2rust/package.json new file mode 100644 index 000000000..38dc915c2 --- /dev/null +++ b/packages/xyd-opencli2rust/package.json @@ -0,0 +1,26 @@ +{ + "name": "@xyd-js/opencli2rust", + "version": "0.1.0-build.0", + "description": "Generate a buildable Rust CLI (clap v4) from an OpenCLI document with the x-openapi request binding, with regen-safe custom-code seams", + "main": "./dist/index.js", + "type": "module", + "scripts": { + "clean": "rimraf dist", + "prebuild": "pnpm clean", + "build": "tsup", + "test": "vitest", + "ci:test": "vitest run", + "format": "bunx biome check --write src" + }, + "dependencies": { + "@xyd-js/opencli": "workspace:*", + "@xyd-js/opensdk-framework": "workspace:*" + }, + "devDependencies": { + "@types/node": "^20.9.0", + "rimraf": "^3.0.2", + "tsup": "^8.3.0", + "typescript": "^5.6.2", + "vitest": "^2.1.1" + } +} diff --git a/packages/xyd-opencli2rust/src/cli.ts b/packages/xyd-opencli2rust/src/cli.ts new file mode 100644 index 000000000..6587d8bb6 --- /dev/null +++ b/packages/xyd-opencli2rust/src/cli.ts @@ -0,0 +1,72 @@ +// `src/gen/cli.rs` emission: the root clap tree, custom-command grafting, and +// the custom-first dispatch (a registered custom command overrides generated +// behavior on the same path). + +import type { OpencliSpecJson } from '@xyd-js/opencli'; + +import type { ResourceFile } from './command'; +import { chain, indent, lit, rsFile, rsStr, Uses, type RsVal } from './rslit'; + +export function renderCli(spec: OpencliSpecJson, binName: string, resources: ResourceFile[]): string { + const calls: [string, RsVal[]][] = []; + const usage = spec.info?.summary || spec.info?.description; + if (usage) calls.push(['about', [rsStr(usage)]]); + if (spec.info?.version) calls.push(['version', [rsStr(spec.info.version)]]); + calls.push(['subcommand_required', [lit('true')]]); + calls.push(['arg_required_else_help', [lit('true')]]); + for (const r of resources) calls.push(['subcommand', [lit(`cmd::${r.modName}::command()`)]]); + const root = chain(`clap::Command::new(${JSON.stringify(binName)})`, calls); + + const rootFn = `/// The generated root command tree. +pub fn root_command() -> clap::Command { + ${root(1)} +}`; + + const arms = resources.map((r) => `Some(${JSON.stringify(r.cmdName)}) => cmd::${r.modName}::run(&ctx, &o, &cmd_path, leaf).await,`); + const matchBody = [ + ...arms, + '_ => {', + ' eprintln!("error: unknown command: {}", cmd_path.join(" "));', + ' ExitCode::FAILURE', + '}', + ].join('\n'); + + const runFn = `/// Parse args, resolve the invoked command path, and dispatch — custom commands +/// first (so user registrations can override generated behavior), then the +/// generated handlers. +pub async fn run(o: O, customs: CustomCommands) -> ExitCode { + let matches = customs.graft(root_command()).get_matches(); + let ctx = Context::from_env(); + let (cmd_path, leaf) = descend(&matches); + + if let Some(handler) = customs.find(&cmd_path) { + return match handler(ctx.clone(), leaf.clone()).await { + Ok(()) => ExitCode::SUCCESS, + Err(err) => o.print_error(&cmd_path, &err), + }; + } + + match cmd_path.first().map(String::as_str) { +${indent(matchBody, 2)} + } +}`; + + const descendFn = `/// Walk matches to the invoked leaf, collecting the full command path. +fn descend(matches: &ArgMatches) -> (Vec, &ArgMatches) { + let mut path = Vec::new(); + let mut current = matches; + while let Some((name, sub)) = current.subcommand() { + path.push(name.to_string()); + current = sub; + } + (path, current) +}`; + + const uses = new Uses().add( + 'std::process::ExitCode', + 'clap::ArgMatches', + 'super::cmd', + 'super::runtime::{CliOverrides, Context, CustomCommands}', + ); + return rsFile(uses, [rootFn, runFn, descendFn]); +} diff --git a/packages/xyd-opencli2rust/src/command.ts b/packages/xyd-opencli2rust/src/command.ts new file mode 100644 index 000000000..f04b4f5f5 --- /dev/null +++ b/packages/xyd-opencli2rust/src/command.ts @@ -0,0 +1,104 @@ +// Per-resource file emission (the command.ts mirror from opencli2go): one +// `src/gen/cmd/.rs` per top-level command with a clap builder tree, +// a flat command-path dispatch, and the leaf handlers. + +import type { Command } from '@xyd-js/opencli'; + +import { renderFlagArgs, renderPositionalArg } from './flags'; +import { renderHandler, type RenderedHandler } from './handler'; +import { buildLeafModel } from './model'; +import { snakeCase } from './naming'; +import { chain, indent, lit, rsFile, rsStr, Uses, type RsVal } from './rslit'; + +interface Leaf { + pathNames: string[]; + command: Command; +} + +function renderCommandChain(command: Command, pathNames: string[], leaves: Leaf[], state: { hasArg: boolean }): RsVal { + const calls: [string, RsVal[]][] = []; + if (command.aliases?.length === 1) calls.push(['visible_alias', [rsStr(command.aliases[0])]]); + else if (command.aliases?.length) + calls.push(['visible_aliases', [lit(`[${command.aliases.map((a) => JSON.stringify(a)).join(', ')}]`)]]); + if (command.description) calls.push(['about', [rsStr(command.description)]]); + if (command.hidden) calls.push(['hide', [lit('true')]]); + + if (command.commands?.length) { + calls.push(['subcommand_required', [lit('true')]]); + calls.push(['arg_required_else_help', [lit('true')]]); + for (const sub of command.commands) { + calls.push(['subcommand', [renderCommandChain(sub, [...pathNames, sub.name], leaves, state)]]); + } + } else if (command['x-openapi']) { + const model = buildLeafModel(command); + for (const arg of command.arguments || []) { + state.hasArg = true; + calls.push(['arg', [renderPositionalArg(arg)]]); + } + for (const flagVal of renderFlagArgs(model.flags)) { + state.hasArg = true; + calls.push(['arg', [flagVal]]); + } + leaves.push({ pathNames, command }); + } else { + // A doc-only command with neither subcommands nor a binding: let clap show help. + calls.push(['subcommand_required', [lit('true')]]); + calls.push(['arg_required_else_help', [lit('true')]]); + } + + return chain(`Command::new(${JSON.stringify(command.name)})`, calls); +} + +export interface ResourceFile { + path: string; + content: string; + /** Rust module name (snake_case, keyword-guarded) — the file base + `cmd::`. */ + modName: string; + /** The raw command name, for the dispatch match in gen/cli.rs. */ + cmdName: string; +} + +/** Render one `src/gen/cmd/.rs` for a top-level command + its subtree. */ +export function renderResourceFile(topCommand: Command): ResourceFile { + const leaves: Leaf[] = []; + const state = { hasArg: false }; + const tree = renderCommandChain(topCommand, [topCommand.name], leaves, state); + + const handlers: RenderedHandler[] = []; + const arms: string[] = []; + for (const leaf of leaves) { + const handler = renderHandler(leaf.pathNames, leaf.command); + handlers.push(handler); + const pattern = `[${leaf.pathNames.map((n) => JSON.stringify(n)).join(', ')}]`; + arms.push(`${pattern} => ${handler.name}(ctx, o, cmd_path, m).await,`); + } + + const commandFn = `pub fn command() -> Command { + ${tree(1)} +}`; + + const params = leaves.length + ? 'ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches' + : '_ctx: &Context, _o: &O, cmd_path: &[String], _m: &ArgMatches'; + const matchBody = [ + ...arms, + '_ => {', + ' eprintln!("error: unknown command: {}", cmd_path.join(" "));', + ' ExitCode::FAILURE', + '}', + ].join('\n'); + const runFn = `pub async fn run(${params}) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { +${indent(matchBody, 2)} + } +}`; + + const uses = new Uses().add('std::process::ExitCode'); + uses.add(state.hasArg ? 'clap::{Arg, ArgMatches, Command}' : 'clap::{ArgMatches, Command}'); + uses.add('crate::gen::runtime::{self, CliOverrides, Context}'); + const content = rsFile(uses, [commandFn, runFn, ...handlers.map((h) => h.code)]); + + const modName = snakeCase(topCommand.name); + return { path: `src/gen/cmd/${modName}.rs`, content, modName, cmdName: topCommand.name }; +} diff --git a/packages/xyd-opencli2rust/src/flags.ts b/packages/xyd-opencli2rust/src/flags.ts new file mode 100644 index 000000000..00dc7dd05 --- /dev/null +++ b/packages/xyd-opencli2rust/src/flags.ts @@ -0,0 +1,55 @@ +// clap Arg emission for flags + positionals (the flags.ts mirror from opencli2go). +// Bool flags accept both `--x` and `--x=false` (num_args 0..=1 + require_equals), +// matching urfave/cli's bool semantics; slices accept repetition and commas. + +import type { FlagModel } from './model'; +import { chain, lit, rsStr, type RsVal } from './rslit'; + +export function renderFlagArg(f: FlagModel): RsVal { + const calls: [string, RsVal[]][] = [['long', [rsStr(f.flagName)]]]; + + const shorts = f.aliases.filter((a) => a.length === 1); + const longs = f.aliases.filter((a) => a.length > 1); + if (shorts.length) calls.push(['short', [lit(`'${shorts[0]}'`)]]); + if (longs.length === 1) calls.push(['visible_alias', [rsStr(longs[0])]]); + else if (longs.length > 1) calls.push(['visible_aliases', [lit(`[${longs.map((l) => JSON.stringify(l)).join(', ')}]`)]]); + + switch (f.flagType) { + case 'int': + calls.push(['value_parser', [lit('clap::value_parser!(i64)')]]); + break; + case 'float': + calls.push(['value_parser', [lit('clap::value_parser!(f64)')]]); + break; + case 'bool': + calls.push(['num_args', [lit('0..=1')]]); + calls.push(['require_equals', [lit('true')]]); + calls.push(['default_missing_value', [rsStr('true')]]); + calls.push(['value_parser', [lit('clap::value_parser!(bool)')]]); + break; + case 'slice': + calls.push(['action', [lit('clap::ArgAction::Append')]]); + calls.push(['value_delimiter', [lit("','")]]); + break; + default: + break; // string | json | file: clap's default String parsing + } + + if (f.description) calls.push(['help', [rsStr(f.description)]]); + if (f.required) calls.push(['required', [lit('true')]]); + if (f.hidden) calls.push(['hide', [lit('true')]]); + + return chain(`Arg::new(${JSON.stringify(f.flagName)})`, calls); +} + +export function renderFlagArgs(flags: FlagModel[]): RsVal[] { + return flags.map(renderFlagArg); +} + +/** A positional argument (clap requires declaring positionals; Go read them untyped). */ +export function renderPositionalArg(arg: { name: string; required?: boolean; description?: string }): RsVal { + const calls: [string, RsVal[]][] = []; + if (arg.description) calls.push(['help', [rsStr(arg.description)]]); + if (arg.required === true) calls.push(['required', [lit('true')]]); + return chain(`Arg::new(${JSON.stringify(arg.name)})`, calls); +} diff --git a/packages/xyd-opencli2rust/src/handler.ts b/packages/xyd-opencli2rust/src/handler.ts new file mode 100644 index 000000000..5bdb66199 --- /dev/null +++ b/packages/xyd-opencli2rust/src/handler.ts @@ -0,0 +1,175 @@ +// Handler emission (the handler.ts mirror from opencli2go): positional path args, +// IsSet-style guarded query/header population, flatten-vs-json body assembly — +// ending in runtime::run_request so the CliOverrides hooks see every request. + +import type { Command } from '@xyd-js/opencli'; + +import { buildLeafModel, type FlagModel, type FlagType, type LeafModel } from './model'; +import { snakeCase } from './naming'; +import { indent } from './rslit'; + +const q = (s: string) => JSON.stringify(s); + +function getOne(id: string, t: FlagType): string { + switch (t) { + case 'bool': + return `m.get_one::(${q(id)})`; + case 'int': + return `m.get_one::(${q(id)})`; + case 'float': + return `m.get_one::(${q(id)})`; + default: + return `m.get_one::(${q(id)})`; // string | json | file + } +} + +/** The `String`-typed rendering of a read flag value, for query/header params. */ +function stringValue(t: FlagType): string { + return t === 'string' || t === 'json' || t === 'file' ? 'v.clone()' : 'v.to_string()'; +} + +function pathParts(model: LeafModel): { fmt: string; args: string[]; usedVars: Set } { + const segs = model.path.split(/(\{[^}]+\})/).filter((s) => s !== ''); + let fmt = ''; + const args: string[] = []; + const usedVars = new Set(); + for (const seg of segs) { + const m = seg.match(/^\{(.+)\}$/); + if (m) { + const a = model.pathArgs.find((p) => p.wireName === m[1]); + fmt += '{}'; + if (a) { + args.push(`runtime::path_escape(${a.varName})`); + usedVars.add(a.varName); + } else { + args.push('""'); + } + } else { + fmt += seg.replace(/\{/g, '{{').replace(/\}/g, '}}'); + } + } + return { fmt, args, usedVars }; +} + +/** Guarded param population for query/header/cookie flags. */ +function paramLines(flags: FlagModel[], target: string): string[] { + const lines: string[] = []; + for (const f of flags) { + if (f.flagType === 'slice') { + lines.push(`if let Some(vs) = m.get_many::(${q(f.flagName)}) {`); + lines.push(` ${target}.push((${q(f.wireName)}, vs.cloned().collect::>().join(",")));`); + lines.push('}'); + } else { + lines.push(`if let Some(v) = ${getOne(f.flagName, f.flagType)} {`); + lines.push(` ${target}.push((${q(f.wireName)}, ${stringValue(f.flagType)}));`); + lines.push('}'); + } + } + return lines; +} + +export interface RenderedHandler { + name: string; + code: string; +} + +/** Render the async handler fn for a leaf command from its x-openapi binding. */ +export function renderHandler(pathNames: string[], command: Command): RenderedHandler { + const model = buildLeafModel(command); + // Snake the WHOLE joined name (not per segment): a keyword segment like + // "match" needs no trailing-underscore guard inside a longer identifier, + // and per-segment guards would produce non-snake-case `__` doubles. + const name = snakeCase(['handle', ...pathNames].join('-')); + + const lines: string[] = []; + const push = (s = '') => lines.push(s); + + // Path params (positional args) — only read the ones the path actually uses. + const { fmt, args, usedVars } = pathParts(model); + for (const a of model.pathArgs) { + if (usedVars.has(a.varName)) { + push(`let ${a.varName} = m.get_one::(${q(a.argName)}).map(String::as_str).unwrap_or("");`); + } + } + push(args.length ? `let path = format!(${q(fmt)}, ${args.join(', ')});` : `let path = ${q(model.path)}.to_string();`); + + // Query params. + const queryFlags = model.flags.filter((f) => f.location === 'query'); + if (queryFlags.length) { + push(`let mut query: Vec<(&'static str, String)> = Vec::new();`); + for (const line of paramLines(queryFlags, 'query')) push(line); + } + + // Header / cookie params. + const headerFlags = model.flags.filter((f) => f.location === 'header' || f.location === 'cookie'); + if (headerFlags.length) { + push(`let mut headers: Vec<(&'static str, String)> = Vec::new();`); + for (const line of paramLines(headerFlags, 'headers')) push(line); + } + + // Body. + let bodyExpr: string | undefined; + if (model.hasBody) { + if (model.bodyStyle === 'json' && model.bodyJsonOption) { + push(`let body_bytes = m.get_one::(${q(model.bodyJsonOption)}).map(|v| v.clone().into_bytes());`); + bodyExpr = 'body_bytes'; + } else { + push('let mut body = serde_json::Map::new();'); + for (const f of model.flags.filter((x) => x.location === 'body')) { + if (f.flagType === 'slice') { + push(`if let Some(vs) = m.get_many::(${q(f.flagName)}) {`); + push( + ` body.insert(${q(f.wireName)}.to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect()));`, + ); + push('}'); + } else if (f.flagType === 'json') { + // Accept either JSON (objects/arrays) or a bare scalar: a string|object + // union field is commonly given a plain value, and an unparseable value + // must not abort the whole request. + push(`if let Some(v) = m.get_one::(${q(f.flagName)}) {`); + push(' let value = serde_json::from_str::(v)'); + push(' .unwrap_or_else(|_| serde_json::Value::String(v.clone()));'); + push(` body.insert(${q(f.wireName)}.to_string(), value);`); + push('}'); + } else { + const value = + f.flagType === 'int' || f.flagType === 'float' + ? 'serde_json::Value::from(*v)' + : f.flagType === 'bool' + ? 'serde_json::Value::Bool(*v)' + : 'serde_json::Value::String(v.clone())'; + push(`if let Some(v) = ${getOne(f.flagName, f.flagType)} {`); + push(` body.insert(${q(f.wireName)}.to_string(), ${value});`); + push('}'); + } + } + push('let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) {'); + push(' Ok(bytes) => bytes,'); + push(' Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())),'); + push('};'); + bodyExpr = 'Some(body_bytes)'; + } + } + + // Assemble the request. + push('let req = runtime::Request {'); + push(` method: ${q(model.method)},`); + push(' path,'); + push(queryFlags.length ? ' query,' : ' query: Vec::new(),'); + push(headerFlags.length ? ' headers,' : ' headers: Vec::new(),'); + push(bodyExpr ? ` body: ${bodyExpr},` : ' body: None,'); + push('};'); + push('runtime::run_request(ctx, o, cmd_path, req).await'); + + const readsMatches = usedVars.size > 0 || model.flags.length > 0; + const mParam = readsMatches ? 'm' : '_m'; + const code = `async fn ${name}( + ctx: &Context, + o: &O, + cmd_path: &[String], + ${mParam}: &ArgMatches, +) -> ExitCode { +${indent(lines.join('\n'))} +}`; + return { name, code }; +} diff --git a/packages/xyd-opencli2rust/src/index.ts b/packages/xyd-opencli2rust/src/index.ts new file mode 100644 index 000000000..667063940 --- /dev/null +++ b/packages/xyd-opencli2rust/src/index.ts @@ -0,0 +1,3 @@ +export { opencli2rust } from './project'; +export { writeProject } from './write'; +export type { Opencli2RustOptions } from './types'; diff --git a/packages/xyd-opencli2rust/src/main.ts b/packages/xyd-opencli2rust/src/main.ts new file mode 100644 index 000000000..4d45c32d2 --- /dev/null +++ b/packages/xyd-opencli2rust/src/main.ts @@ -0,0 +1,20 @@ +// `src/main.rs` emission: the ~10-line wiring seam between the user-owned +// custom module and the generated CLI. Deliberately trivial so users never +// need to touch it (it stays generator-owned, writeMode overwrite). + +import { GENERATED_HEADER } from './rslit'; + +export function renderMain(): string { + return `${GENERATED_HEADER} + +mod custom; +mod gen; + +#[tokio::main] +async fn main() -> std::process::ExitCode { + let mut commands = gen::runtime::CustomCommands::new(); + custom::register(&mut commands); + gen::cli::run(custom::overrides(), commands).await +} +`; +} diff --git a/packages/xyd-opencli2rust/src/model.ts b/packages/xyd-opencli2rust/src/model.ts new file mode 100644 index 000000000..1f5a8d01b --- /dev/null +++ b/packages/xyd-opencli2rust/src/model.ts @@ -0,0 +1,153 @@ +// The x-openapi -> request-model normalization, mirroring xyd-opencli2go's model.ts +// with language-neutral renames (GoType -> FlagType, PathArg.goVar -> varName). + +import type { Command, Option, XOpenApiCommand } from '@xyd-js/opencli'; + +import { snakeCase } from './naming'; + +export type FlagType = 'string' | 'int' | 'float' | 'bool' | 'slice' | 'json' | 'file'; + +export interface PathArg { + /** Rust local variable name for the positional (snake_case, keyword-guarded). */ + varName: string; + /** The clap Arg id — the OpenCLI argument name, verbatim. */ + argName: string; + wireName: string; + idx: number; +} + +export interface FlagModel { + flagName: string; + wireName: string; + location: 'query' | 'header' | 'cookie' | 'body'; + flagType: FlagType; + required: boolean; + hidden: boolean; + aliases: string[]; + description?: string; +} + +export interface LeafModel { + method: string; // uppercase HTTP method + path: string; + pathArgs: PathArg[]; + flags: FlagModel[]; + hasBody: boolean; + bodyStyle?: string; // 'flatten' | 'json' | 'multipart' + bodyJsonOption?: string; // flag name when the whole body is one JSON option (style=json) +} + +function fromToken(from: string | undefined, kind: 'option' | 'argument'): string | undefined { + if (!from) return undefined; + const [k, ...rest] = from.split(':'); + return k === kind ? rest.join(':') : undefined; +} + +function encodingToFlagType(encoding: string | undefined): FlagType | undefined { + switch (encoding) { + case 'integer': + return 'int'; + case 'number': + return 'float'; + case 'boolean': + return 'bool'; + case 'array': + return 'slice'; + case 'json': + return 'json'; + case 'file': + return 'file'; + case 'string': + return 'string'; + default: + return undefined; + } +} + +function optionFlagType(opt: Option, encoding?: string): FlagType { + if (!opt.arguments || opt.arguments.length === 0) return 'bool'; + const fromEncoding = encodingToFlagType(encoding); + if (fromEncoding) return fromEncoding; + const arg = opt.arguments[0]; + if (arg.arity) return 'slice'; + switch (arg.name) { + case 'integer': + return 'int'; + case 'number': + return 'float'; + case 'boolean': + return 'bool'; + case 'json': + return 'json'; + default: + return 'string'; + } +} + +/** Build the normalized request model for a leaf command from its x-openapi binding. */ +export function buildLeafModel(command: Command): LeafModel { + const x = command['x-openapi'] as XOpenApiCommand; + const params = x.params || []; + const body = x.body; + + const pathArgs: PathArg[] = (command.arguments || []).map((arg, idx) => { + const param = params.find((p) => p.in === 'path' && fromToken(p.from, 'argument') === arg.name); + return { varName: snakeCase(arg.name), argName: arg.name, wireName: param?.name ?? arg.name, idx }; + }); + + const bodyPropByOption = new Map(); // flagName -> encoding + const bodyWireByOption = new Map(); // flagName -> wire name + for (const prop of body?.properties || []) { + const flag = fromToken(prop.from, 'option'); + if (flag) { + bodyPropByOption.set(flag, prop.encoding || 'string'); + bodyWireByOption.set(flag, prop.name); + } + } + + const flags: FlagModel[] = []; + for (const opt of command.options || []) { + const paramEntry = params.find((p) => fromToken(p.from, 'option') === opt.name && p.in !== 'path'); + let location: FlagModel['location']; + let wireName: string; + let encoding: string | undefined; + + if (paramEntry) { + location = paramEntry.in as FlagModel['location']; + wireName = paramEntry.name; + } else if (bodyPropByOption.has(opt.name)) { + location = 'body'; + wireName = bodyWireByOption.get(opt.name) ?? opt.name; + encoding = bodyPropByOption.get(opt.name); + } else { + location = 'query'; + wireName = opt.name; + } + + flags.push({ + flagName: opt.name, + wireName, + location, + flagType: optionFlagType(opt, encoding), + required: opt.required === true, + hidden: opt.hidden === true, + aliases: opt.aliases || [], + description: opt.description, + }); + } + + const bodyStyle = body?.style; + const bodyJsonOption = + bodyStyle === 'json' ? fromToken(body?.from, 'option') ?? flags.find((f) => f.location === 'body')?.flagName : undefined; + const hasBody = !!body && (bodyStyle === 'json' || flags.some((f) => f.location === 'body')); + + return { + method: (x.method || 'get').toUpperCase(), + path: x.path, + pathArgs, + flags, + hasBody, + bodyStyle, + bodyJsonOption, + }; +} diff --git a/packages/xyd-opencli2rust/src/naming.ts b/packages/xyd-opencli2rust/src/naming.ts new file mode 100644 index 000000000..06cb6006c --- /dev/null +++ b/packages/xyd-opencli2rust/src/naming.ts @@ -0,0 +1,56 @@ +// Rust naming helpers, mirroring xyd-opensdk-rust's naming.ts (plus `slug` from +// xyd-opencli2go). Copied by repo convention: generators own their emission helpers. + +/** Split an identifier (camel/snake/kebab/space/dot) into lowercase words. */ +export function splitWords(input: string): string[] { + return input + .replace(/([a-z0-9])([A-Z])/g, '$1 $2') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') + .split(/[^a-zA-Z0-9]+/) // split on ANY non-alphanumeric so wire junk (e.g. `ids[]`) can't leak into an identifier + .map((w) => w.trim().toLowerCase()) + .filter(Boolean); +} + +/** Prefix `_` when an identifier would start with a digit (illegal as a bare Rust name). */ +function safeIdent(s: string): string { + return /^[0-9]/.test(s) ? `_${s}` : s; +} + +/** PascalCase for Rust structs/enums/variants, e.g. "pet-list" -> "PetList". */ +export function pascalCase(input: string): string { + const s = splitWords(input) + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(''); + return safeIdent(s) || 'Value'; +} + +/** snake_case for Rust modules/fields/fns, keyword-guarded with a trailing `_`. */ +export function snakeCase(input: string): string { + const s = safeIdent(splitWords(input).join('_')) || 'field'; + return RUST_KEYWORDS.has(s) ? `${s}_` : s; +} + +/** SCREAMING_SNAKE_CASE env-var prefix, e.g. "sample-api" -> "SAMPLE_API". */ +export function screamingSnakeCase(input: string): string { + return safeIdent(splitWords(input).join('_').toUpperCase().replace(/[^A-Z0-9_]/g, '')) || 'VALUE'; +} + +/** A Cargo package name (snake_case, alnum + underscore), e.g. "My API" -> "my_api". */ +export function crateName(input: string): string { + const s = splitWords(input).join('_').replace(/[^a-z0-9_]/g, ''); + return safeIdent(s) || 'cli'; +} + +/** Slug for a binary name, e.g. "Sample API" -> "sample-api". */ +export function slug(input: string): string { + return splitWords(input).join('-').replace(/[^a-z0-9-]/g, ''); +} + +/** Reserved + weak Rust keywords that can never be a bare identifier. */ +const RUST_KEYWORDS = new Set([ + 'as', 'break', 'const', 'continue', 'crate', 'dyn', 'else', 'enum', 'extern', 'false', 'fn', 'for', + 'if', 'impl', 'in', 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub', 'ref', 'return', 'self', + 'static', 'struct', 'super', 'trait', 'true', 'type', 'unsafe', 'use', 'where', 'while', 'async', + 'await', 'abstract', 'become', 'box', 'do', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', + 'virtual', 'yield', 'try', 'union', +]); diff --git a/packages/xyd-opencli2rust/src/project.ts b/packages/xyd-opencli2rust/src/project.ts new file mode 100644 index 000000000..90c9a167b --- /dev/null +++ b/packages/xyd-opencli2rust/src/project.ts @@ -0,0 +1,59 @@ +import type { OpencliSpecJson } from '@xyd-js/opencli'; +import type { ProjectFileMap } from '@xyd-js/opensdk-framework'; + +import { renderCli } from './cli'; +import { renderResourceFile, type ResourceFile } from './command'; +import { renderMain } from './main'; +import { crateName as toCrateName, slug } from './naming'; +import { GENERATED_HEADER } from './rslit'; +import { + cargoToml, + configRs, + customRegistryRs, + customScaffoldRs, + genModRs, + httpRs, + overridesRs, + runtimeModRs, +} from './runtime'; +import type { Opencli2RustOptions } from './types'; + +/** + * Generate a buildable Rust CLI project (clap v4, async reqwest runtime) from an + * OpenCLI document. Pure: returns a virtual file map for the framework's + * writeProject — generator-owned files as plain strings ('overwrite'), user-owned + * scaffolds (Cargo.toml, .gitignore, src/custom/) as 'skipIfExists' entries. + */ +export function opencli2rust(spec: OpencliSpecJson, options: Opencli2RustOptions = {}): ProjectFileMap { + const binName = options.binName ?? (slug(spec.info?.title || 'cli') || 'cli'); + const crate = options.crateName ?? toCrateName(spec.info?.title || binName); + const edition = options.edition ?? '2021'; + const baseURL = options.baseURL ?? spec['x-openapi']?.servers?.[0] ?? ''; + + const files: ProjectFileMap = {}; + + files['Cargo.toml'] = { content: cargoToml(spec, crate, binName, edition), writeMode: 'skipIfExists' }; + files['.gitignore'] = { content: '/target\n', writeMode: 'skipIfExists' }; + + files['src/main.rs'] = renderMain(); + + const resources: ResourceFile[] = (spec.commands || []).map((top) => renderResourceFile(top)); + for (const r of resources) files[r.path] = r.content; + files['src/gen/cmd/mod.rs'] = cmdModRs(resources); + files['src/gen/cli.rs'] = renderCli(spec, binName, resources); + files['src/gen/mod.rs'] = genModRs(); + files['src/gen/runtime/mod.rs'] = runtimeModRs(); + files['src/gen/runtime/http.rs'] = httpRs(); + files['src/gen/runtime/config.rs'] = configRs(spec, binName, baseURL); + files['src/gen/runtime/overrides.rs'] = overridesRs(); + files['src/gen/runtime/custom.rs'] = customRegistryRs(); + + files['src/custom/mod.rs'] = { content: customScaffoldRs(), writeMode: 'skipIfExists' }; + + return files; +} + +function cmdModRs(resources: ResourceFile[]): string { + const mods = resources.map((r) => `pub mod ${r.modName};`).join('\n'); + return `${GENERATED_HEADER}\n${mods ? `\n${mods}\n` : ''}`; +} diff --git a/packages/xyd-opencli2rust/src/rslit.ts b/packages/xyd-opencli2rust/src/rslit.ts new file mode 100644 index 000000000..942ab1c27 --- /dev/null +++ b/packages/xyd-opencli2rust/src/rslit.ts @@ -0,0 +1,91 @@ +// Tiny Rust-source string helpers (the golit.ts analogue for clap builder chains): +// just enough to render method-call chains with correct indentation — not an AST. + +const UNIT = ' '; // 4-space Rust indentation +const pad = (n: number) => UNIT.repeat(n); + +/** A value renderer: its first line is emitted inline; nested lines indent to `indent`. */ +export type RsVal = (indent: number) => string; + +/** A pre-rendered scalar (identifier, number, expression). */ +export const lit = (s: string): RsVal => () => s; + +/** A Rust double-quoted string literal (JSON escaping is Rust-compatible for our content). */ +export const rsStr = (s: string): RsVal => () => JSON.stringify(s); + +/** + * A builder-method chain: `base` inline, then one `.method(args)` per line at + * `indent + 1`. A multiline argument goes on its own lines (rustfmt style): + * + * Command::new("models") + * .about("...") + * .subcommand( + * Command::new("list"), + * ) + */ +export function chain(base: string, calls: [method: string, args: RsVal[]][]): RsVal { + return (indent: number) => { + let out = base; + for (const [method, args] of calls) { + const rendered = args.map((a) => a(indent + 2)); + if (rendered.some((r) => r.includes('\n'))) { + const block = rendered.map((r) => `${pad(indent + 2)}${r},`).join('\n'); + out += `\n${pad(indent + 1)}.${method}(\n${block}\n${pad(indent + 1)})`; + } else { + out += `\n${pad(indent + 1)}.${method}(${rendered.join(', ')})`; + } + } + return out; + }; +} + +/** Render a top-level value (indent 0). */ +export function render(v: RsVal): string { + return v(0); +} + +/** Indent every non-empty line of `text` by `levels` indent units (blank lines stay blank). */ +export function indent(text: string, levels = 1): string { + const p = pad(levels); + return text + .split('\n') + .map((line) => (line.length ? p + line : line)) + .join('\n'); +} + +/** Tracks the `use` items a generated file needs, rendered as one sorted block. */ +export class Uses { + private items = new Set(); + + add(...uses: string[]): this { + for (const u of uses) this.items.add(u); + return this; + } + + get size(): number { + return this.items.size; + } + + /** Groups: std first, external crates next, `crate::`/`super::` last — blank-line separated. */ + render(): string { + if (this.items.size === 0) return ''; + const all = [...this.items]; + const std = all.filter((u) => u.startsWith('std::')).sort(); + const local = all.filter((u) => /^(crate|super|self)::/.test(u)).sort(); + const ext = all.filter((u) => !std.includes(u) && !local.includes(u)).sort(); + const groups = [std, ext, local].filter((g) => g.length > 0); + return groups.map((g) => g.map((u) => `use ${u};`).join('\n')).join('\n\n'); + } +} + +/** Assemble a complete generated Rust file: ownership header + uses + declarations. */ +export function rsFile(uses: Uses, decls: string[], header = GENERATED_HEADER): string { + const parts: string[] = []; + if (header) parts.push(header); + if (uses.size > 0) parts.push(uses.render()); + parts.push(...decls); + return `${parts.join('\n\n')}\n`; +} + +/** The ownership header stamped on every generator-owned file. */ +export const GENERATED_HEADER = '// Code generated by opencli2rust. DO NOT EDIT.'; diff --git a/packages/xyd-opencli2rust/src/runtime.ts b/packages/xyd-opencli2rust/src/runtime.ts new file mode 100644 index 000000000..99d1d77e8 --- /dev/null +++ b/packages/xyd-opencli2rust/src/runtime.ts @@ -0,0 +1,478 @@ +// The vendored Rust runtime + user-owned scaffolds, embedded as template strings +// (the runtime.ts pattern from xyd-opencli2go). One deliberate divergence from the +// Go runtime: execution RETURNS the decoded value instead of printing inside Do — +// printing lives exclusively in the CliOverrides trait so custom code owns output. + +import type { OpencliSpecJson, XOpenApiSecurity } from '@xyd-js/opencli'; + +import { screamingSnakeCase } from './naming'; + +const HEADER = '// Code generated by opencli2rust. DO NOT EDIT.'; + +/** Static: `src/gen/runtime/http.rs` — request/error primitives. */ +export function httpRs(): string { + return `${HEADER} + +//! Static HTTP primitives shared by every command handler. + +use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; +use serde_json::Value; + +/// Escape a value for use inside a URL path segment (strict RFC 3986 unreserved). +const PATH_SEGMENT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'_').remove(b'.').remove(b'~'); + +pub fn path_escape(value: &str) -> String { + utf8_percent_encode(value, PATH_SEGMENT).to_string() +} + +/// A generic API request assembled by a command handler. +pub struct Request { + pub method: &'static str, + pub path: String, + pub query: Vec<(&'static str, String)>, + pub headers: Vec<(&'static str, String)>, + pub body: Option>, +} + +/// A request failure, inspectable from \`CliOverrides::recover_error\` / \`print_error\`. +#[derive(Debug)] +pub enum Error { + /// The server answered with a 4xx/5xx status; \`body\` is the decoded response. + Http { status: u16, body: Value }, + /// The request could not be sent or the response could not be read. + Transport(String), + /// A handler-side failure (flag decoding, body assembly, custom command logic). + Invalid(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Http { status, body } => write!(f, "request failed: {status}: {body}"), + Error::Transport(message) => write!(f, "request error: {message}"), + Error::Invalid(message) => write!(f, "{message}"), + } + } +} + +impl std::error::Error for Error {} + +/// Decode a response body: JSON when it parses, the raw text otherwise. +pub(crate) fn decode(bytes: &[u8]) -> Value { + serde_json::from_slice(bytes).unwrap_or_else(|_| Value::String(String::from_utf8_lossy(bytes).into_owned())) +} +`; +} + +/** Static: `src/gen/runtime/mod.rs` — Context (execution) + run_request (hook threading). */ +export function runtimeModRs(): string { + return `${HEADER} + +//! The vendored runtime: request execution context, override hooks, and the +//! custom-command registry. Extension points live in \`overrides.rs\` (the +//! \`CliOverrides\` trait) and \`custom.rs\` (the \`CustomCommands\` registry). + +pub mod config; +mod custom; +mod http; +mod overrides; + +pub use custom::CustomCommands; +pub use http::{path_escape, Error, Request}; +pub use overrides::CliOverrides; + +use std::process::ExitCode; + +use serde_json::Value; + +/// Everything a handler (generated or custom) needs to reach the API: the +/// configured base URL and an HTTP client with the spec's auth applied. +#[derive(Clone)] +pub struct Context { + pub base_url: String, + http: reqwest::Client, +} + +impl Context { + pub fn from_env() -> Self { + Self { + base_url: config::base_url(), + http: reqwest::Client::new(), + } + } + + /// Execute against the configured base URL with the spec's auth applied, + /// decoding the response (JSON when it parses, raw text otherwise). + pub async fn execute(&self, req: Request) -> Result { + let (status, bytes) = self.send(req).await?; + let value = http::decode(&bytes); + if status >= 400 { + return Err(Error::Http { status, body: value }); + } + Ok(value) + } + + /// Raw-bytes variant of [\`execute\`](Self::execute) for binary/non-JSON endpoints. + #[allow(dead_code)] // user-facing API for custom command handlers + pub async fn execute_raw(&self, req: Request) -> Result, Error> { + let (status, bytes) = self.send(req).await?; + if status >= 400 { + return Err(Error::Http { status, body: http::decode(&bytes) }); + } + Ok(bytes) + } + + async fn send(&self, req: Request) -> Result<(u16, Vec), Error> { + let endpoint = format!("{}{}", self.base_url.trim_end_matches('/'), req.path); + let method = reqwest::Method::from_bytes(req.method.as_bytes()).map_err(|e| Error::Invalid(e.to_string()))?; + let mut rb = self.http.request(method, &endpoint); + if !req.query.is_empty() { + rb = rb.query(&req.query); + } + let has_content_type = req.headers.iter().any(|(k, _)| k.eq_ignore_ascii_case("content-type")); + for (key, value) in &req.headers { + rb = rb.header(*key, value.as_str()); + } + if let Some(body) = req.body { + if !has_content_type { + rb = rb.header("Content-Type", "application/json"); + } + rb = rb.body(body); + } + rb = config::apply_auth(rb); + let resp = rb.send().await.map_err(|e| Error::Transport(e.to_string()))?; + let status = resp.status().as_u16(); + let bytes = resp.bytes().await.map_err(|e| Error::Transport(e.to_string()))?.to_vec(); + Ok((status, bytes)) + } +} + +/// Thread one generated request through the override hooks: mutate, execute, +/// transform, recover, then print. Printing happens ONLY here (via the trait), +/// so overrides fully control the CLI's output. +pub async fn run_request( + ctx: &Context, + o: &O, + cmd_path: &[String], + mut req: Request, +) -> ExitCode { + o.before_request(ctx, cmd_path, &mut req); + let outcome = match ctx.execute(req).await { + Ok(value) => o.transform_response(ctx, cmd_path, value), + Err(err) => Err(err), + }; + match outcome.or_else(|err| o.recover_error(ctx, cmd_path, err)) { + Ok(value) => { + o.print_success(cmd_path, &value); + ExitCode::SUCCESS + } + Err(err) => o.print_error(cmd_path, &err), + } +} +`; +} + +/** Static: `src/gen/runtime/overrides.rs` — the CliOverrides trait (all methods defaulted + sync). */ +export function overridesRs(): string { + return `${HEADER} + +use std::process::ExitCode; + +use serde_json::Value; + +use super::http::{Error, Request}; +use super::Context; + +/// The override seam for every generated command. Implement this on your type in +/// \`src/custom/mod.rs\` — every method has a default, so \`impl CliOverrides for +/// Custom {}\` compiles and behaves exactly like the stock CLI. +pub trait CliOverrides { + /// Mutate the assembled request before it is sent (extra headers, alternate + /// auth, tracing). \`cmd_path\` is the invoked command path, e.g. \`["models", "list"]\`. + fn before_request(&self, ctx: &Context, cmd_path: &[String], req: &mut Request) { + let _ = (ctx, cmd_path, req); + } + + /// Transform the decoded response before printing. + fn transform_response(&self, ctx: &Context, cmd_path: &[String], value: Value) -> Result { + let _ = (ctx, cmd_path); + Ok(value) + } + + /// Recover from a failure: return \`Ok(value)\` to turn it into a success, + /// or hand the error back to keep failing. + fn recover_error(&self, ctx: &Context, cmd_path: &[String], err: Error) -> Result { + let _ = (ctx, cmd_path); + Err(err) + } + + /// Print a successful result. Default: pretty JSON (raw text for non-JSON bodies). + fn print_success(&self, cmd_path: &[String], value: &Value) { + let _ = cmd_path; + match value { + Value::String(text) => println!("{text}"), + other => match serde_json::to_string_pretty(other) { + Ok(text) => println!("{text}"), + Err(_) => println!("{other}"), + }, + } + } + + /// Print a failure and choose the process exit code. + fn print_error(&self, cmd_path: &[String], err: &Error) -> ExitCode { + let _ = cmd_path; + eprintln!("error: {err}"); + ExitCode::FAILURE + } +} +`; +} + +/** Static: `src/gen/runtime/custom.rs` — the custom-command registry + clap grafting. */ +export function customRegistryRs(): string { + return `${HEADER} + +use std::future::Future; +use std::pin::Pin; + +use clap::{ArgMatches, Command}; + +use super::http::Error; +use super::Context; + +/// The boxed async handler for one custom command (owned inputs, so plain +/// \`|ctx, m| async move { ... }\` closures fit without lifetime plumbing). +pub type CustomHandler = Box Pin>>>>; + +struct CustomEntry { + under: Vec, + command: Command, + handler: CustomHandler, +} + +/// Hand-written commands registered from \`src/custom/mod.rs\`, grafted into the +/// generated tree before parsing and dispatched BEFORE generated handlers — a +/// custom command registered on an existing path overrides its behavior. +#[derive(Default)] +pub struct CustomCommands { + entries: Vec, +} + +impl CustomCommands { + pub fn new() -> Self { + Self::default() + } + + /// Add \`command\` under a parent path (\`&[]\` = top level): + /// \`add(&["tools"], Command::new("hello"), |ctx, m| async move { ... })\` + /// registers \` tools hello\`. Missing parents are created. When the full + /// path already exists in the generated tree, the generated clap definition + /// keeps handling parsing/help, but YOUR handler runs instead. + #[allow(dead_code)] // user-facing API, called from src/custom/mod.rs + pub fn add(&mut self, under: &[&str], command: Command, handler: H) + where + H: Fn(Context, ArgMatches) -> F + 'static, + F: Future> + 'static, + { + self.entries.push(CustomEntry { + under: under.iter().map(|s| s.to_string()).collect(), + command, + handler: Box::new(move |ctx, m| Box::pin(handler(ctx, m))), + }); + } + + /// Graft registered commands into the generated clap tree (new paths only — + /// existing definitions are left untouched; dispatch still prefers custom). + pub(crate) fn graft(&self, mut root: Command) -> Command { + for entry in &self.entries { + let mut full = entry.under.clone(); + full.push(entry.command.get_name().to_string()); + if path_exists(&root, &full) { + continue; + } + root = graft_at(root, &entry.under, entry.command.clone()); + } + root + } + + /// The handler registered for exactly this invoked command path, if any. + pub(crate) fn find(&self, path: &[String]) -> Option<&CustomHandler> { + self.entries + .iter() + .find(|e| { + e.under.len() + 1 == path.len() + && e.under.iter().zip(path.iter()).all(|(a, b)| a == b) + && path.last().map(String::as_str) == Some(e.command.get_name()) + }) + .map(|e| &e.handler) + } +} + +fn path_exists(root: &Command, path: &[String]) -> bool { + let mut current = root; + for name in path { + match current.find_subcommand(name) { + Some(sub) => current = sub, + None => return false, + } + } + true +} + +fn graft_at(parent: Command, under: &[String], cmd: Command) -> Command { + match under.split_first() { + None => parent.subcommand(cmd), + Some((head, rest)) => { + let rest = rest.to_vec(); + if parent.find_subcommand(head).is_some() { + parent.mut_subcommand(head.clone(), move |sub| graft_at(sub, &rest, cmd)) + } else { + let node = Command::new(head.clone()).subcommand_required(true).arg_required_else_help(true); + parent.subcommand(graft_at(node, &rest, cmd)) + } + } + } +} +`; +} + +/** User-owned scaffold: `src/custom/mod.rs` (written once, `skipIfExists`). */ +export function customScaffoldRs(): string { + return `//! Yours — scaffolded once by opencli2rust, never overwritten by regeneration. +//! +//! Two extension seams, both optional: +//! * \`CliOverrides\` on [\`Custom\`] — hook/override every generated command +//! (see src/gen/runtime/overrides.rs for the available methods). +//! * [\`register\`] — add fully custom commands anywhere in the tree; a command +//! registered on an existing path takes over that command's behavior. + +use crate::gen::runtime::{CliOverrides, CustomCommands}; + +/// Override only what you need — every trait method has a default. +pub struct Custom; + +impl CliOverrides for Custom {} + +/// Register hand-written commands. Called by the generated \`main\`. +pub fn register(commands: &mut CustomCommands) { + let _ = commands; + // Example — a fully custom \`tools hello\` subcommand: + // + // commands.add(&["tools"], clap::Command::new("hello").about("Say hello"), |_ctx, _m| async move { + // println!("hello"); + // Ok(()) + // }); +} + +pub fn overrides() -> Custom { + Custom +} +`; +} + +/** Static: `src/gen/mod.rs`. */ +export function genModRs(): string { + return `${HEADER} + +pub mod cli; +pub mod cmd; +pub mod runtime; +`; +} + +function authBlock(scheme: XOpenApiSecurity): string | null { + const env = scheme.envVar || 'API_KEY'; + const name = scheme.name || ''; + switch (scheme.kind) { + case 'bearer': + return ` if let Ok(v) = std::env::var(${JSON.stringify(env)}) { + if !v.is_empty() { + rb = rb.bearer_auth(v); + } + }`; + case 'apiKey-header': + return ` if let Ok(v) = std::env::var(${JSON.stringify(env)}) { + if !v.is_empty() { + rb = rb.header(${JSON.stringify(name)}, v); + } + }`; + case 'apiKey-query': + return ` if let Ok(v) = std::env::var(${JSON.stringify(env)}) { + if !v.is_empty() { + rb = rb.query(&[(${JSON.stringify(name)}, v.as_str())]); + } + }`; + case 'apiKey-cookie': + return ` if let Ok(v) = std::env::var(${JSON.stringify(env)}) { + if !v.is_empty() { + rb = rb.header("Cookie", format!("{}={}", ${JSON.stringify(name)}, v)); + } + }`; + case 'basic': + return ` let user = std::env::var(${JSON.stringify(`${env}_USERNAME`)}).unwrap_or_default(); + let pass = std::env::var(${JSON.stringify(`${env}_PASSWORD`)}).unwrap_or_default(); + if !user.is_empty() || !pass.is_empty() { + rb = rb.basic_auth(user, Some(pass)); + }`; + default: + return null; + } +} + +/** Generated: `src/gen/runtime/config.rs` — base URL + auth derived from the spec's root x-openapi. */ +export function configRs(spec: OpencliSpecJson, binName: string, baseURL: string): string { + const prefix = screamingSnakeCase(binName); + const security = spec['x-openapi']?.security || []; + const blocks = security.map(authBlock).filter((b): b is string => b !== null); + + const applyAuth = blocks.length + ? `/// Attach credentials read from the environment to the request. +pub fn apply_auth(mut rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder { +${blocks.join('\n')} + rb +}` + : `/// Attach credentials read from the environment to the request. +pub fn apply_auth(rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder { + rb +}`; + + return `${HEADER} + +//! Spec-derived configuration: the API base URL and environment-based auth. + +const DEFAULT_BASE_URL: &str = ${JSON.stringify(baseURL)}; + +/// The API base URL, overridable via \`${prefix}_BASE_URL\`. +pub fn base_url() -> String { + match std::env::var(${JSON.stringify(`${prefix}_BASE_URL`)}) { + Ok(v) if !v.is_empty() => v, + _ => DEFAULT_BASE_URL.to_string(), + } +} + +${applyAuth} +`; +} + +/** Generated once (`skipIfExists`): the crate manifest — user-owned after first generation. */ +export function cargoToml(spec: OpencliSpecJson, crate: string, binName: string, edition: string): string { + const version = spec.info?.version || '0.1.0'; + return `# Generated once by opencli2rust — yours to edit; regeneration never overwrites it. + +[package] +name = ${JSON.stringify(crate)} +version = ${JSON.stringify(version)} +edition = ${JSON.stringify(edition)} + +[dependencies] +clap = { version = "4", features = ["string"] } +percent-encoding = "2" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +serde_json = "1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } + +[[bin]] +name = ${JSON.stringify(binName)} +path = "src/main.rs" +`; +} diff --git a/packages/xyd-opencli2rust/src/types.ts b/packages/xyd-opencli2rust/src/types.ts new file mode 100644 index 000000000..113c00122 --- /dev/null +++ b/packages/xyd-opencli2rust/src/types.ts @@ -0,0 +1,13 @@ +export interface Opencli2RustOptions { + /** Cargo package name for the generated crate. Default: crate_name(info.title). */ + crateName?: string; + /** Binary name (`[[bin]]` name, root command name, env-var prefix). Default: slug(info.title). */ + binName?: string; + /** Rust edition in Cargo.toml. Default: "2021". */ + edition?: string; + /** + * Default API base URL baked into the generated runtime (overridable at runtime + * via `_BASE_URL`). Default: the first `x-openapi.servers` entry. + */ + baseURL?: string; +} diff --git a/packages/xyd-opencli2rust/src/write.ts b/packages/xyd-opencli2rust/src/write.ts new file mode 100644 index 000000000..cc8cfc6a3 --- /dev/null +++ b/packages/xyd-opencli2rust/src/write.ts @@ -0,0 +1,18 @@ +// The framework's full regen lifecycle (per-file writeMode, `.sdk/sdk.lock` +// manifest, guarded stale-prune, `.sdkignore` user-owned protection, and opt-in +// `{ merge: true }` 3-way merge of hand-edits) — with this generator's identity. + +import { + writeProject as frameworkWriteProject, + type ProjectFileMap, + type WriteProjectOptions, + type WriteProjectResult, +} from '@xyd-js/opensdk-framework'; + +export async function writeProject( + files: ProjectFileMap, + outDir: string, + options: WriteProjectOptions = {}, +): Promise { + return frameworkWriteProject(files, outDir, { generator: 'opencli2rust', ...options }); +} diff --git a/packages/xyd-opencli2rust/tsconfig.json b/packages/xyd-opencli2rust/tsconfig.json new file mode 100644 index 000000000..82cd344d6 --- /dev/null +++ b/packages/xyd-opencli2rust/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "declaration": true, + "declarationMap": true, + "incremental": true, + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo" + }, + "include": ["index.ts", "src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/xyd-opencli2rust/tsup.config.ts b/packages/xyd-opencli2rust/tsup.config.ts new file mode 100644 index 000000000..f6c261116 --- /dev/null +++ b/packages/xyd-opencli2rust/tsup.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['index.ts'], + format: ['esm', 'cjs'], + target: 'node16', + dts: { + entry: 'index.ts', + resolve: true, + }, + splitting: false, + sourcemap: true, + clean: true, + esbuildOptions: (options) => { + options.platform = 'node'; + options.external = ['node:fs/promises']; + options.loader = { '.js': 'jsx' }; + }, +}); diff --git a/packages/xyd-opencli2rust/vitest.config.ts b/packages/xyd-opencli2rust/vitest.config.ts new file mode 100644 index 000000000..4fd5cf03b --- /dev/null +++ b/packages/xyd-opencli2rust/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + }, +}) From 969a6b82da08d0a070a8e8198cc201ba647f1ae7 Mon Sep 17 00:00:00 2001 From: Patryk Zdunowski Date: Sun, 2 Aug 2026 02:18:12 +0200 Subject: [PATCH 2/5] feat(opensdk): allow CLI output targets (go-cli/rust-cli) in generate, sdk.json and chains --- .../OpenCliCliGeneration.md | 80 +++++- .../13.api-definitions/OpenSdkGeneration.md | 271 ++++++++++++++++++ .dev/docs/GLOSSARY.md | 9 + .github/workflows/tests-opensdk-pipeline.yml | 5 + CLAUDE.md | 1 + .../__tests__/cli-targets.test.ts | 227 +++++++++++++++ .../__tests__/e2e/cli-chain.test.ts | 69 +++++ packages/xyd-opensdk-cli/package.json | 5 + .../xyd-opensdk-cli/src/cli/cli-targets.ts | 158 ++++++++++ packages/xyd-opensdk-cli/src/cli/generate.ts | 61 ++-- packages/xyd-opensdk-cli/src/cli/index.ts | 9 +- packages/xyd-opensdk-cli/src/cli/publish.ts | 9 + .../xyd-opensdk-cli/src/cli/write-report.ts | 23 ++ packages/xyd-opensdk-cli/src/index.ts | 4 + .../xyd-opensdk-schemas/chain.schema.json | 259 ++++++++++++++++- .../scripts/gen-schema.mjs | 86 +++++- packages/xyd-opensdk-schemas/sdk.schema.json | 266 +++++++++++++++++ pnpm-lock.yaml | 60 +++- 18 files changed, 1566 insertions(+), 36 deletions(-) create mode 100644 .dev/docs/13.api-definitions/OpenSdkGeneration.md create mode 100644 packages/xyd-opensdk-cli/__tests__/cli-targets.test.ts create mode 100644 packages/xyd-opensdk-cli/__tests__/e2e/cli-chain.test.ts create mode 100644 packages/xyd-opensdk-cli/src/cli/cli-targets.ts create mode 100644 packages/xyd-opensdk-cli/src/cli/write-report.ts diff --git a/.dev/docs/13.api-definitions/OpenCliCliGeneration.md b/.dev/docs/13.api-definitions/OpenCliCliGeneration.md index 8232539bc..8efa674b3 100644 --- a/.dev/docs/13.api-definitions/OpenCliCliGeneration.md +++ b/.dev/docs/13.api-definitions/OpenCliCliGeneration.md @@ -2,8 +2,9 @@ This document describes the **OpenAPI → OpenCLI → CLI** pipeline: how xyd turns an OpenAPI 3.x spec into a *functional* command-line interface that makes real HTTP requests. It covers the -three packages involved, the `x-openapi` request-binding extension, the mapping algorithm, the -Go generator, and the test/CI setup. +packages involved, the `x-openapi` request-binding extension, the mapping algorithm, the +Go and Rust generators (including the Rust generator's regen-safe custom-code seams), and the +test/CI setup. > The user-facing guide is not published yet (the feature is merged without official user docs); > this page is the under-the-hood view. @@ -20,6 +21,7 @@ HTTP request. graph LR OAS["OpenAPI 3.x"] -->|"@xyd-js/openapi2opencli\n(Stage A)"| OCLI["OpenCLI doc\n(+ x-openapi)"] OCLI -->|"@xyd-js/opencli2go"| GO["Go CLI project"] + OCLI -->|"@xyd-js/opencli2rust"| RUST["Rust CLI project\n(+ custom-code seams)"] OCLI -.->|"future: 2py / 2ts"| OTHER["other languages"] CORE["@xyd-js/opencli\n(core model + helpers)"] --- OCLI @@ -27,6 +29,7 @@ graph LR style OAS fill:#fdcb6e,color:#333,stroke:#d4a94e style OCLI fill:#6c5ce7,color:#fff,stroke:#5a4bd4 style GO fill:#00b894,color:#fff,stroke:#009a7a + style RUST fill:#00b894,color:#fff,stroke:#009a7a style OTHER fill:#dfe6e9,color:#333,stroke:#b2bec3 style CORE fill:#636e72,color:#fff,stroke:#4a5558 ``` @@ -38,6 +41,7 @@ graph LR | `@xyd-js/opencli` | Core OpenCLI model + helpers (extracted from `@xyd-js/opencli-remark`) | `OpencliSpecJson`, `Command`, `loadOpencliSpec()`, `findCommand()`, `generate*()` | | `@xyd-js/openapi2opencli` | **Stage A** — OpenAPI → OpenCLI (+ `x-openapi`) | `openapi2opencli()`, `openapi2opencliFromSource()`, `opencliToSurface()`, `diffSurfaces()` | | `@xyd-js/opencli2go` | OpenCLI → buildable Go CLI project | `opencli2go()`, `writeProject()` | +| `@xyd-js/opencli2rust` | OpenCLI → buildable Rust CLI project with regen-safe custom-code seams | `opencli2rust()`, `writeProject()` | ### @xyd-js/opencli (core model) @@ -98,6 +102,51 @@ and avoids a Go toolchain dependency at generation time). from positionals, set query params/body from flags, attach auth from the configured env var, call the vendored client, and print the response. +### @xyd-js/opencli2rust (Rust generator) + +The Rust sibling of `opencli2go`: same layering (`project.ts` / `command.ts` / `handler.ts` / +`flags.ts` / `model.ts` / `runtime.ts`), same templated-emitter approach (`rslit.ts` renders +clap builder-method chains; Rust naming helpers copied from `xyd-opensdk-rust`). Stack: +**clap v4 (builder API)** + async **reqwest/tokio**; the vendored runtime executes requests +and returns the decoded value. Request-level behavior is e2e-verified byte-compatible with the +Go generator against the shared `recorded.json` fixtures. + +Two deliberate divergences from the Go generator: + +1. **`ProjectFileMap` output + framework write lifecycle.** `opencli2rust()` returns the + `@xyd-js/opensdk-framework` `ProjectFileMap` (entries carry per-file `writeMode`), and its + `writeProject` re-exports the framework's — `.sdk/sdk.lock` manifest, guarded stale-prune, + `.sdkignore`, and opt-in `{ merge: true }` 3-way merge (instead of opencli2go's 10-line + file dumper). +2. **Custom-code seams** (the Fern / Oxide-progenitor concept). The generated crate splits + `src/gen/**` (regenerated, "DO NOT EDIT") from `src/custom/mod.rs` (scaffolded once, + `skipIfExists`). Three extension points, wired through the generated `main.rs`: + - the `CliOverrides` trait (`before_request` / `transform_response` / `recover_error` / + `print_success` / `print_error`, all defaulted; printing lives ONLY in the trait, which + is why the runtime returns values instead of printing like Go's `runtime.Do`); + - the `CustomCommands` registry — `commands.add(&["tools"], Command::new("hello"), |ctx, m| async …)` + grafts new commands anywhere in the clap tree, and a registration on an EXISTING path + overrides that command's behavior (custom-first dispatch); + - `Context` (`execute` / `execute_raw`) so custom handlers reuse the CLI's base URL + auth. + The `5.custom-scaffold` fixture suite is the acceptance test: a customized scaffold survives + regen byte-identically, compiles, and merge-mode hand-edits to `src/gen/**` survive a + spec-changing regen via 3-way merge. + +### `opensdk` integration (`go-cli` / `rust-cli` targets) + +The pipeline has a command-line entry point through the **`opensdk` CLI** (see +[OpenSDK Generation](./OpenSdkGeneration.md)): the pseudo-language target ids `go-cli` and +`rust-cli` work in `opensdk generate --lang`, sdk.json sections, and `chain.json` targets. +`opensdk-cli`'s `src/cli/cli-targets.ts` routes them before the emitter registry (CLI +generation consumes the raw OpenAPI doc, not the OpenSDK IR), runs +`openapi2opencliFromSource` → `opencli2go`/`opencli2rust`, and writes through the framework +`writeProject` — so the regen lifecycle (lock, stale-prune, `.sdkignore`, `--merge`) applies to +BOTH backends, including Go (whose own `write.ts` stays the naive standalone variant). Options +are one flat bag split by allowlist (converter vs backend keys); `sdkName` defaults `cliName`. +Chain example: `packages/apitoolchain-sdk-chain/chain.json` target `api-cli` +(`target: "rust-cli"`). The `tests-opensdk-pipeline` CI job compiles chain-generated CLI +targets for both backends. + ## The `x-openapi` extension This is what makes generation *functional*. Shape: @@ -117,7 +166,12 @@ following the repo's [fixture convention](../2.1.development/4.TESTS_AND_FIXTURE | Stage | Fixture dir | Files per method | |-------|-------------|------------------| | Stage A | `xyd-openapi2opencli/__fixtures__/-2.complex.openai//` | `input.json` (OpenAPI op) | -| Generator | `xyd-opencli2go/__fixtures__/-2.complex.openai//` | `input.json` (OpenCLI), `output.go`, `recorded.json` | +| Go generator | `xyd-opencli2go/__fixtures__/-2.complex.openai//` | `input.json` (OpenCLI), `output.go`, `recorded.json` | +| Rust generator | `xyd-opencli2rust/__fixtures__/-2.complex.openai//` | `input.json` + `recorded.json` (synced COPIES from the Go package via `O2R_BUILD_DOCS=1`), `output.rs` | + +The Go package is the single source of truth for fixture generation (from the vendored OpenAPI +oracle) and request recording (`E2E_RECORD`); the Rust package keeps independent copies of the +language-neutral files so it stays self-contained. ### Conformance oracle @@ -151,10 +205,12 @@ run stays offline and deterministic: | Env var | Effect | Runs in CI? | |---------|--------|-------------| -| `O2G_GO_SMOKE=1` | `go build` / `go vet` a sample of generated projects | Yes (Go job) | -| `E2E_CLI=1` | build the whole CLI, run it, diff requests vs fixtures | Yes (Go job) | -| `O2G_BUILD_DOCS=1` | **regenerate** the Stage-B `input.json` / `output.go` goldens | No | -| `E2E_RECORD=1` | **regenerate** the per-method `recorded.json` | No | +| `O2G_GO_SMOKE=1` | `go build` / `go vet` a sample of generated Go projects | Yes (pipeline job) | +| `O2R_CARGO_SMOKE=1` | `cargo check` generated Rust projects (shared `CARGO_TARGET_DIR`) | Yes (pipeline job) | +| `E2E_CLI=1` | build the whole CLI (Go and/or Rust), run it, diff requests vs fixtures | Yes (pipeline job) | +| `O2G_BUILD_DOCS=1` | **regenerate** the Go `input.json` / `output.go` goldens | No | +| `O2R_BUILD_DOCS=1` | **re-sync** the Rust package's `input.json`/`recorded.json` copies + regen `output.rs` | No | +| `E2E_RECORD=1` | **regenerate** the per-method `recorded.json` (Go package only) | No | > Known gap, surfaced by the fixtures: the generated runtime always assembles a JSON body > (multipart `--file` uploads are not wired yet). On macOS, generated urfave binaries may fail @@ -166,11 +222,13 @@ run stays offline and deterministic: | Workflow | Covers | Toolchain | |----------|--------|-----------| | `tests-unit.yml` (`tests:unit`) | each package's **offline** unit tests (auto-discovered by the root `vitest.config.ts` glob) | Node + pnpm | -| `tests-opencli-pipeline.yml` (`tests:opencli-pipeline`) | the **Go-gated** layers excluded by the root config: `O2G_GO_SMOKE=1` + `E2E_CLI=1` for `@xyd-js/opencli2go`, plus the e2e binding guard | Node + pnpm + **Go 1.22** | +| `tests-opencli-pipeline.yml` (`tests:opencli-pipeline`) | the **toolchain-gated** layers excluded by the root config: `O2G_GO_SMOKE=1` + `E2E_CLI=1` for `@xyd-js/opencli2go`, `O2R_CARGO_SMOKE=1` + `E2E_CLI=1` for `@xyd-js/opencli2rust`, plus the e2e binding guards | Node + pnpm + **Go 1.22** + **Rust stable** | The root `vitest.config.ts` `include` glob (`packages/**/__tests__/**/*.test.ts`) already runs -the three packages' offline tests in `tests:unit`, but it **excludes `**/__tests__/e2e/**`**. -`tests-opencli-pipeline.yml` therefore sets up a Go toolchain and runs each package's +the pipeline packages' offline tests in `tests:unit`, but it **excludes `**/__tests__/e2e/**`**. +`tests-opencli-pipeline.yml` therefore sets up Go + Rust toolchains and runs each package's package-local `ci:test` (whose config does not exclude e2e) with the gate env vars, so the full pipeline — including the real generated-CLI requests — is verified where binaries execute. It is -`paths`-scoped to the three packages to avoid running the heavier Go job on unrelated pushes. +`paths`-scoped to the pipeline packages to avoid running the heavier toolchain job on unrelated +pushes. The Rust job shares one `CARGO_TARGET_DIR` (cached with `~/.cargo`) so dependencies +compile once across smoke samples and the e2e build. diff --git a/.dev/docs/13.api-definitions/OpenSdkGeneration.md b/.dev/docs/13.api-definitions/OpenSdkGeneration.md new file mode 100644 index 000000000..d6aafa8f8 --- /dev/null +++ b/.dev/docs/13.api-definitions/OpenSdkGeneration.md @@ -0,0 +1,271 @@ +# OpenSDK Generation + +This document describes the **OpenAPI → OpenSDK IR → SDK** pipeline: how xyd turns an OpenAPI +3.x spec into typed, functional client SDKs for seven languages. It covers the `xyd-opensdk-*` +package family, the IR, the emitter plugin contract, the regen-safe write lifecycle, the +`opensdk` CLI, the chain pipeline, and the test/CI setup. + +> Sibling pipeline: [OpenCLI CLI generation](./OpenCliCliGeneration.md) turns specs into +> command-line tools; both share the regen-safe `writeProject` lifecycle described below. + +## Overview + +The conversion is split into composable, independently testable stages. The intermediate +format is the **OpenSDK IR** (`OpensdkSpecJson`) — a normalized description of an API client: +a symbol table of named types, a nested resource tree of typed methods with HTTP bindings, +and a declarative runtime-behavior block. + +```mermaid +graph LR + OAS["OpenAPI 3.x"] -->|"@xyd-js/openapi2opensdk\n(Stage A)"| IR["OpenSDK IR\n(OpensdkSpecJson)"] + IR -->|"framework orchestrator\n+ emitter plugins"| SDKS["SDK projects\n(go · node · python · ruby\n· java · dotnet · rust)"] + + CHAIN["@xyd-js/opensdk-chain\n(multi-source + overlays)"] --> OAS + CORE["@xyd-js/opensdk-core\n(IR types + behavior + config + diff)"] --- IR + CLI["opensdk CLI"] -.->|"parse / generate / run\n/ diff / publish"| IR + IR -->|"@xyd-js/opensdk-uniform"| DOCS["docs code samples\n(Uniform References)"] + + style OAS fill:#fdcb6e,color:#333,stroke:#d4a94e + style IR fill:#6c5ce7,color:#fff,stroke:#5a4bd4 + style SDKS fill:#00b894,color:#fff,stroke:#009a7a + style CHAIN fill:#a29bfe,color:#fff,stroke:#8b83e8 + style CLI fill:#4a9eff,color:#fff,stroke:#3a7fd4 + style CORE fill:#636e72,color:#fff,stroke:#4a5558 + style DOCS fill:#fd79a8,color:#fff,stroke:#d4608a +``` + +## Packages + +| Package | Role | +|---------|------| +| `@xyd-js/opensdk-core` | Layer-0: IR types (`OpensdkSpecJson`), `SdkBehavior` defaults + merge, spec helpers (`loadOpensdkSpec`, `walkMethods`), config shapes (`SdkJson`, `ChainJson`), `diffIR` breaking-change classifier | +| `@xyd-js/opensdk-schemas` | JSON Schemas: `sdk.schema.json` (validates sdk.json) + `chain.schema.json` (validates chain.json), generated from core's `opensdk-spec.json` | +| `@xyd-js/openapi2opensdk` | **Stage A** — OpenAPI → OpenSDK IR; also the conformance "surface" utilities | +| `@xyd-js/opensdk-framework` | The `Emitter` plugin contract, orchestrator, language registry, `planOperation`/`planExample`, and the regen-safe `writeProject` lifecycle | +| `@xyd-js/opensdk-{go,node,python,ruby,java,dotnet,rust}` | Per-language emitters (7) | +| `@xyd-js/opensdk-cli` | The `opensdk` binary: `parse` / `generate` / `diff` / `publish` / `run` / `init` | +| `@xyd-js/opensdk-chain` | `chain.json` pipeline: multi-source OpenAPI merge + Overlay 1.0.0, then generate/publish per target | +| `@xyd-js/opensdk-merge` | `merge3(base, ours, theirs)` — the 3-way line merge behind `{ merge: true }` regeneration | +| `@xyd-js/opensdk-ci` | Shared test infra: goldens, compile smokes, recording/mock servers, behavior parity, publish round-trips | +| `@xyd-js/opensdk-uniform` | Docs integration: enrich Uniform References with per-language SDK snippets + type references | + +## The OpenSDK IR (`@xyd-js/opensdk-core`) + +`opensdk-spec.json` (JSON Schema) is the single source of truth: `src/types.ts` is generated +from it via `json-schema-to-typescript`, and `xyd-opensdk-schemas` lifts its `$defs` into the +config schemas. + +```ts +interface OpensdkSpecJson { + opensdk: string; // format version + info: SdkInfo; + servers?: string[]; + security?: SdkSecurity[]; // normalized: kind = bearer | apiKey-header/query/cookie | basic | other (+ envVar) + types?: NamedType[]; // symbol table: struct | enum | union | alias + resources?: Resource[]; // nested resource tree; each Method = typed SDK call + HTTP binding + sdk?: SdkBehavior; // declarative runtime behavior +} +``` + +| Type | Role | +|------|------| +| `NamedType` | Named type in the symbol table; `kind` discriminates `struct \| enum \| union \| alias` | +| `Resource` / `Method` | Client tree node / typed method with HTTP binding (action, httpMethod, path, params, body, responses, pagination, security) | +| `Param` / `Field` | Path/query/header param · struct field (wire name, required/nullable/deprecated/default) | +| `TypeRef` | Structural reference: `scalar \| ref \| array \| map \| any` | +| `Pagination` | `style: cursor \| page \| offset` + items/cursor/offset/limit/next field names | + +**`SdkBehavior`** is the declarative "how SDKs behave at runtime" block, resolved by +`sdkBehavior(spec)` (defaults deep-merged with overrides; arrays replace): retry +(maxRetries=2, retryable status codes, exponential backoff + jitter), timeout (60s), error +mapping, user-agent (incl. AI-agent env detection: `CLAUDE_CODE`, `CURSOR_AGENT`, …), +telemetry headers, logging, idempotency-key injection for retried POSTs, auto-page delay, and +request-guard (misplaced-option detection). Every emitter must render the same behavior — see +"parity" under Tests. + +**`diffIR(base, head)`** classifies IR changes as `breaking | risky | safe` across ~30 kinds +(`method-removed`, `param-type-changed`, `field-required-flip`, `enum-value-removed`, …) — +this powers `opensdk diff --fail-on breaking`. + +## Stage A: `@xyd-js/openapi2opensdk` + +```ts +openapi2opensdk(doc: OpenAPIV3.Document, options?: OpenApi2OpenSdkOptions): OpensdkSpecJson +openapi2opensdkFromSource(source: string, options?): Promise +``` + +Options: `sdkName`, `includeMethods`/`includePaths`, `verbMap`/`customActionVerbs`, +`authEnvVar`, `operationHints`, `mountRules` (resource-tree regrouping), `sdkBehavior`. + +| Module | Purpose | +|--------|---------| +| `nominal.ts` | `SymbolTable` — resolves `$ref`-keyed component schemas into `NamedType[]`, preserving nominal identity (works on the RAW, un-dereferenced doc) | +| `resourceTree.ts` | Builds the nested `Resource[]` tree from operations | +| `action.ts` | `deriveTarget()` — resource path + action verb from method + URL shape (list/retrieve/create/update/delete + trailing verbs) | +| `method.ts` / `schema.ts` / `security.ts` | Per-operation `Method` construction · OpenAPI schema helpers · security normalization | +| `surface.ts` | `opensdkToSurface()` + `diffSurfaces()` — reduce the IR and a real SDK's parsed surface to a canonical shape and diff them (the conformance oracle mechanism, same idea as the OpenCLI pipeline's) | + +## The framework (`@xyd-js/opensdk-framework`) + +Emitters are **plugins** implementing the `Emitter` contract; capability methods are PURE +(IR in, files out — no IO): + +```ts +interface Emitter { + language: string; + fileHeader?(ctx): string | null; // optional ownership header + generateProject(spec, ctx): GeneratedFile[]; // manifest, README, configs + generateClient(spec, ctx): GeneratedFile[]; + generateTypes(types, ctx): GeneratedFile[]; + generateResources(resources, ctx): GeneratedFile[]; + generateRuntime(spec, ctx): GeneratedFile[]; + generateTests?(spec, ctx): GeneratedFile[]; // optional + generateUsage?(method, chain, ctx): string; // optional: docs snippets + generateTypeReference?(method, chain, ctx): RenderedTypeReference; // optional: docs types +} +``` + +- `orchestrator.ts` — `generate()` / `generateFileMap()` drive the capabilities in order, + prepend the file header, and assemble the virtual file map (duplicate paths throw). +- `registry.ts` — `registerEmitter()` / `getEmitter()` / `resolveLanguage()` with aliases + (`ts`/`typescript`/`js` → node, `rs` → rust, `c#`/`.net` → dotnet, …). `applyConfig()` lets + an `opensdk.config.*` file register **custom emitters as plugins**. +- `operation-plan.ts` — `planOperation(method, types)` → semantic plan (page class name, + `encoding: json | multipart | form`, param groups, primary-response classification, + idempotency injection) so emitters share one interpretation of a Method. +- `example-plan.ts` — language-neutral example values, shared by `generateTests` and + `generateUsage` so every language exercises identical shapes. + +### The write lifecycle (regen safety) + +`writeProject(files, outDir, { generator, merge })` is the ONLY fs-touching entry point, +shared by every generator (including `opencli2rust`): + +1. **`.sdkignore`** — user-authored, gitignore-style: matched paths are user-owned (never + overwritten or pruned; divergence reported in `conflicts`). Wins over any writeMode. +2. **Per-file `WriteMode`** — `overwrite` | `skipIfExists` (scaffolds, never clobbered) | + `mergeJson` (deep-merge generated JSON into the user's; user keys win). +3. **`.sdk/sdk.lock`** — hash manifest of pristine generated content; enables the guarded + stale-prune (only pristine orphans are deleted; modified ones are kept → `keptModified`) + and byte-stable no-op regens. +4. **`{ merge: true }`** — hand-edits to `overwrite` files survive regeneration via + `merge3` from `@xyd-js/opensdk-merge` (base = the `.sdk/base/` content-addressed + snapshot, ours = on-disk, theirs = new generation); conflicts get git-style markers → + `mergeConflicts`. + +Result buckets: `written / skipped / unchanged / pruned / keptModified / conflicts / merged / +mergeConflicts`. + +## Emitters + +All generated SDKs are **dependency-light by design** — stdlib HTTP wherever the platform +allows: + +| Package | Generated stack | Smoke gate | +|---------|-----------------|------------| +| `xyd-opensdk-go` | stdlib `net/http` (zero deps) | `O2S_GO_SMOKE` | +| `xyd-opensdk-node` | global `fetch` + built-ins (zero deps, Node 18+) | `O2S_NODE_SMOKE` | +| `xyd-opensdk-python` | stdlib `urllib` | `O2S_PY_SMOKE` | +| `xyd-opensdk-ruby` | stdlib `net/http` + `json` | `O2S_RUBY_SMOKE` | +| `xyd-opensdk-java` | `java.net.http.HttpClient` + hand-rolled JSON codec | `O2S_JAVA_SMOKE` | +| `xyd-opensdk-dotnet` | `System.Net.Http` + `System.Text.Json` | `O2S_DOTNET_SMOKE` | +| `xyd-opensdk-rust` | async `reqwest` (rustls) + `tokio` + `serde` + `thiserror` | `O2S_RUST_SMOKE` | + +Each emitter follows the same internal layout (a writer module with language-literal helpers, +`naming.ts` with keyword guards, per-capability renderers) and ships golden fixtures +(`__fixtures__//input.json` → `output/` tree) plus per-method complex-corpus fixtures. +Representative generated layout (go): `go.mod`, `client.go`, `types.go`, `.go` + +`_test.go`, `option/`, `internal/requestconfig/`, `packages/{apijson,pagination,param}/`. + +## The `opensdk` CLI (`@xyd-js/opensdk-cli`) + +| Command | Purpose / key flags | +|---------|---------------------| +| `opensdk parse` | OpenAPI → IR JSON. `--spec` (required), `--output`, `--sdk-name`, `--grouping` | +| `opensdk generate` | Generate one language (`--lang`, incl. the CLI targets `go-cli`/`rust-cli`) or all configured. `--spec`, `--output`, `--dry-run`, `--no-tests`, **`--merge`** (3-way merge regen) | +| `opensdk diff ` | IR-to-IR breaking-change diff. `--fail-on breaking\|risky\|any`, `--json` | +| `opensdk publish` | Publish a generated SDK. `--lang`, `--output`, `--registry`, `--dry-run` | +| `opensdk run` | Execute a `chain.json` pipeline. `--chain`, `--target`, `--source`, `--publish`, `--dry-run` | +| `opensdk init` | Scaffold config. `--format json\|mjs`, `--lang`, `--chain` | + +Config resolution (root `--config` overrides discovery): **`sdk.json`** (declarative; per-language +sections with `output`/`behavior`/`publish` + emitter options) wins over +**`opensdk.config.{ts,js,mjs}`** (JS plugin bundle — the place to register custom emitters). +`--grouping ` loads `{ mountRules, operationHints }` to reshape the resource tree. + +### CLI output targets (`go-cli` / `rust-cli`) + +The toolchain can also output **command-line tools** via the +[OpenCLI pipeline](./OpenCliCliGeneration.md) (`openapi2opencli` → `opencli2go`/`opencli2rust`), +surfaced as pseudo-language target ids usable anywhere a language is: `--lang rust-cli`, +`"rust-cli": {...}` sdk.json sections, and `target: "rust-cli"` chain targets. Because CLI +generation consumes the **raw OpenAPI doc** (not the OpenSDK IR), these are NOT emitters — +`generateCommand` routes them before the registry (`src/cli/cli-targets.ts`), which also covers +chain targets since the chain engine injects that same function. Mechanics: a section is one +flat option bag split by allowlist (converter keys like `cliName`/`bodyStrategy`/`flagCase` vs +backend keys like `binName`/`crateName`/`modulePath` — disjoint, unit-tested); a pre-parsed IR +`--spec` is rejected with a pointer to pass the OpenAPI doc; SDK-tree grouping +(`mountRules`/`operationHints`) is warned-once + ignored; `--no-tests` is a no-op; `--merge` +and the full framework write lifecycle apply to both backends (Go included — its naive writer +is bypassed); `opensdk publish` and chain `--publish` skip CLI targets with a note (no registry +publisher). Real-world example: `packages/apitoolchain-sdk-chain/chain.json`'s `api-cli` target. + +## Chain (`@xyd-js/opensdk-chain`) + +`chain.json` (`detectChain`: explicit path → `chain.json` → `.chain/chain.json`) declares +named `sources` and `targets`: + +- **Sources**: multiple OpenAPI `inputs` merged at **operation granularity** (paths union per + HTTP method, components union per name, conflicts throw) + `overlays` applied as + **OpenAPI Overlay 1.0.0** documents (JSONPath `target` via `jsonpath-plus`; `remove` deletes, + `update` deep-merges). Overlays are the spec-level customization knob — modify the API + surface BEFORE codegen, complementing the code-level merge story. +- **Targets**: `{ target: , source: , output, behavior, options, publish }`. +- `runChain()` processes each referenced source once, generates every target, optionally + publishes (CLI targets are skipped by publish). + +## Docs integration (`@xyd-js/opensdk-uniform`) + +Bridges SDK generation into the docs site: `attachSdkExamples(references, rawDoc)` enriches +Uniform `Reference`s in place with per-language usage snippets (each emitter's +`generateUsage`), replacing curl-only tabs; `attachSdkTypes` swaps REST param/response +definitions for SDK type references (`generateTypeReference`) with per-language signatures. +`SDK_LANGS` fixes the switcher order (go, python, typescript, ruby, java, csharp). Consumed by +`apps/apitoolchain-web` (`app/lib/openapi/sdkExamples.server.ts`); exposed to plugins as +`opensdkUniformPlugin` / `opensdkTypesUniformPlugin`. + +## Tests and CI + +`@xyd-js/opensdk-ci` is the shared harness (imported by every emitter's tests): + +| Module | Provides | +|--------|----------| +| `golden.ts` / `corpus.ts` | Fixture primitives + discovery of `.complex.` per-method corpora | +| `compile-smoke.ts` | `compileSmoke(lang, dir)` for all 7 languages (tsc / go build / py_compile / ruby -c / javac / dotnet build / cargo build) | +| `e2e.ts` / `sdk-e2e.ts` | `RecordingServer` + request-binding diff: build the real SDK, drive it, diff the actual HTTP request against committed `recorded.json` | +| `mock.ts` | Spec-shaped mock API (in-repo Prism analog) for running each SDK's own generated test suite | +| `parity.ts` | Cross-language behavior parity: distinctive `SdkBehavior` overrides + containment markers every runtime must render | +| `publish.ts` | Publish round-trips: Verdaccio (npm), pypiserver, gemstash; file feeds for dotnet/Maven/Go/Rust | +| `usage-compile.ts` / `snippet-run.ts` | Compile + run the docs usage snippets | + +### Env gates + +| Env var | Effect | Runs in CI? | +|---------|--------|-------------| +| `O2S__SMOKE=1` | whole-SDK compile + usage-snippet compile for that language | Yes | +| `E2E_SDK=1` | real-SDK request-binding diff vs `recorded.json` | Yes | +| `E2E_SDK_TESTS=1` | run each generated SDK's own test suite against the mock server | Yes | +| `E2E_SDK_CHAIN=1` | `opensdk run` + chain.json end-to-end (merge + overlay → all languages compile) | Yes | +| `E2E_SDK_PUBLISH=1` | generate → publish to isolated local registry → install back → load | Yes | +| `O2S_BUILD_DOCS=1` | **regenerate** per-method fixture goldens from the oracle | No | + +### Workflow + +`.github/workflows/tests-opensdk-pipeline.yml` (`tests:opensdk-pipeline`) — paths-scoped to the +opensdk packages; sets up Node + pnpm + Bun + **Go 1.22 + Python 3.11 + Ruby 3.1 + Java 17 + +.NET 8 + Rust stable**, local registries (verdaccio :4873, pypiserver :8081, gemstash :9292), +and runs each package's `ci:test` with the gates above. The conformance oracle (vendored +encrypted spec + reference surfaces under `packages/xyd-openapi2opensdk/oracle/`) is decrypted +via the `XYD_CONTENT_SECRET` repo secret; without it those suites skip gracefully. As with the +OpenCLI job, the root `tests:unit` vitest run covers the offline layers and excludes +`**/__tests__/e2e/**`. diff --git a/.dev/docs/GLOSSARY.md b/.dev/docs/GLOSSARY.md index 170db1356..e643cf622 100644 --- a/.dev/docs/GLOSSARY.md +++ b/.dev/docs/GLOSSARY.md @@ -62,6 +62,9 @@ docs.json / docs.ts ## E +Emitter +: The plugin contract in `@xyd-js/opensdk-framework` that a per-language SDK generator implements (`generateProject/Client/Types/Resources/Runtime` + optional `generateTests/Usage/TypeReference`). Capability methods are pure (IR in, files out); the orchestrator drives them and assembles the virtual file map. See `13.api-definitions/OpenSdkGeneration.md`. + ExampleGroup : A Uniform data type that groups related code examples with a description. Contains an array of `Example` objects, each with a `CodeBlock` of multi-language tabs. @@ -164,6 +167,12 @@ openapi2opencli opencli2go : The `@xyd-js/opencli2go` package. Generates a buildable Go CLI project (urfave/cli v3) from an OpenCLI document as a pure virtual file map. Functional handlers read `x-openapi` to make real HTTP requests. +opencli2rust +: The `@xyd-js/opencli2rust` package. Generates a buildable Rust CLI project (clap v4 + async reqwest) from an OpenCLI document, with regen-safe custom-code seams: a `src/gen/**` vs `src/custom/` split, the `CliOverrides` hook trait, custom-command grafting, and opt-in 3-way merge of hand-edits. See `13.api-definitions/OpenCliCliGeneration.md`. + +OpenSDK +: The SDK-generation toolchain (`xyd-opensdk-*`): OpenAPI → OpenSDK IR (`OpensdkSpecJson`) → per-language SDKs via Emitter plugins (go, node, python, ruby, java, dotnet, rust), driven by the `opensdk` CLI, with declarative runtime behavior (`SdkBehavior`), chain pipelines + OpenAPI Overlays, and the regen-safe `writeProject` lifecycle. See `13.api-definitions/OpenSdkGeneration.md`. + ## P Plugin diff --git a/.github/workflows/tests-opensdk-pipeline.yml b/.github/workflows/tests-opensdk-pipeline.yml index 96b9aa6f2..0fef35f77 100644 --- a/.github/workflows/tests-opensdk-pipeline.yml +++ b/.github/workflows/tests-opensdk-pipeline.yml @@ -265,6 +265,11 @@ jobs: env: E2E_SDK_CHAIN: '1' + - name: Chain e2e — CLI output targets (opensdk run: go-cli + rust-cli, each compiles) + run: pnpm --filter @xyd-js/opensdk-cli exec vitest run __tests__/e2e/cli-chain.test.ts + env: + E2E_SDK_CHAIN: '1' + # ── Publish e2e ────────────────────────────────────────────────────────── # Prove each generated SDK can be published → installed back → loaded. The 3 # HTTP registries (npm/PyPI/RubyGems) run natively on the runner; dotnet/Maven diff --git a/CLAUDE.md b/CLAUDE.md index 460cab2c6..a766d0a17 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,6 +76,7 @@ - @.dev/docs/13.api-definitions/ExamplesAndReferences.md - @.dev/docs/13.api-definitions/OpenApiProcessing.md - @.dev/docs/13.api-definitions/OpenCliCliGeneration.md +- @.dev/docs/13.api-definitions/OpenSdkGeneration.md - @.dev/docs/13.api-definitions/PackageSources.md - @.dev/docs/13.api-definitions/TypeTransformationSystem.md - @.dev/docs/13.api-definitions/McpProcessing.md diff --git a/packages/xyd-opensdk-cli/__tests__/cli-targets.test.ts b/packages/xyd-opensdk-cli/__tests__/cli-targets.test.ts new file mode 100644 index 000000000..8ac7b8eb7 --- /dev/null +++ b/packages/xyd-opensdk-cli/__tests__/cli-targets.test.ts @@ -0,0 +1,227 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { beforeAll, describe, expect, it } from 'vitest'; + +import { resolveLanguage } from '@xyd-js/opensdk-framework'; + +import { + CLI_CONVERTER_KEYS, + cliBackendKeys, + generateCliTarget, + generateCommand, + generateTargets, + isCliTarget, + parseCommand, + publishTarget, + registerBuiltinEmitters, + resolveConfig, + runChain, + splitCliOptions, +} from '../src'; + +// The petstore OpenAPI doc vendored as the converter's 1.basic fixture. +const SPEC = path.join(__dirname, '../../xyd-openapi2opensdk/__fixtures__/1.basic/input.json'); + +const tmp = () => fs.mkdtempSync(path.join(os.tmpdir(), 'opensdk-clitargets-')); +const write = (dir: string, rel: string, body: unknown) => { + const p = path.join(dir, rel); + fs.mkdirSync(path.dirname(p), { recursive: true }); + fs.writeFileSync(p, typeof body === 'string' ? body : JSON.stringify(body, null, 2)); + return p; +}; + +beforeAll(() => registerBuiltinEmitters()); + +describe('cli target routing', () => { + it('recognizes exactly the CLI target ids (no aliases)', () => { + expect(isCliTarget('go-cli')).toBe(true); + expect(isCliTarget('rust-cli')).toBe(true); + expect(isCliTarget('RUST-CLI')).toBe(true); + expect(isCliTarget('go')).toBe(false); + expect(isCliTarget('cli')).toBe(false); + expect(isCliTarget(undefined)).toBe(false); + }); + + it('pins go-cli/rust-cli as unclaimed by the language alias table', () => { + // The routing relies on resolveLanguage passing unknown ids through + // unchanged; a future alias claiming these ids would silently break it. + expect(resolveLanguage('go-cli')).toBe('go-cli'); + expect(resolveLanguage('rust-cli')).toBe('rust-cli'); + }); +}); + +describe('splitCliOptions', () => { + it('converter and backend key sets are disjoint for every backend', () => { + for (const lang of ['go-cli', 'rust-cli']) { + const overlap = (CLI_CONVERTER_KEYS as readonly string[]).filter((k) => cliBackendKeys(lang).includes(k)); + expect(overlap, `${lang} overlap`).toEqual([]); + } + }); + + it('splits a flat mixed bag by allowlist', () => { + const { converter, backend } = splitCliOptions('rust-cli', { + cliName: 'acme', + flagCase: 'kebab', + crateName: 'acme-cli', + baseURL: 'https://api.acme.dev', + }); + expect(converter).toEqual({ cliName: 'acme', flagCase: 'kebab' }); + expect(backend).toEqual({ crateName: 'acme-cli', baseURL: 'https://api.acme.dev' }); + }); + + it('rejects unknown keys, listing the valid ones', () => { + expect(() => splitCliOptions('go-cli', { packageName: 'x' })).toThrow(/Unknown option\(s\) for "go-cli": packageName/); + expect(() => splitCliOptions('go-cli', { packageName: 'x' })).toThrow(/modulePath/); + }); + + it('tolerates and strips the shared `tests` knob', () => { + const { converter, backend } = splitCliOptions('go-cli', { tests: false }); + expect(converter).toEqual({}); + expect(backend).toEqual({}); + }); +}); + +describe('generate --lang go-cli / rust-cli', () => { + it('generates a Go CLI project through the OpenCLI pipeline', async () => { + const dir = tmp(); + try { + await generateCommand({ spec: SPEC, lang: 'go-cli', output: dir, sdkName: 'petstore' }); + expect(fs.existsSync(path.join(dir, 'go.mod'))).toBe(true); + expect(fs.existsSync(path.join(dir, 'cmd/petstore/main.go'))).toBe(true); + expect(fs.existsSync(path.join(dir, 'pkg/cmd/pets.go'))).toBe(true); + // The framework write lifecycle applies to CLI outputs too. + expect(fs.existsSync(path.join(dir, '.sdk/sdk.lock'))).toBe(true); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it('generates a Rust CLI with mixed flat options (converter + backend)', async () => { + const dir = tmp(); + try { + await generateCommand({ + spec: SPEC, + lang: 'rust-cli', + output: dir, + emitterOptions: { cliName: 'petstore', crateName: 'petstore_cli' }, + }); + const cargo = fs.readFileSync(path.join(dir, 'Cargo.toml'), 'utf8'); + expect(cargo).toContain('name = "petstore_cli"'); + expect(fs.existsSync(path.join(dir, 'src/gen/cmd/pets.rs'))).toBe(true); + // The user-owned custom-code scaffold ships with every Rust CLI. + expect(fs.existsSync(path.join(dir, 'src/custom/mod.rs'))).toBe(true); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it('defaults the CLI name from sdkName', async () => { + const dir = tmp(); + try { + await generateCliTarget({ spec: SPEC, lang: 'go-cli', output: dir, sdkName: 'acme' }); + expect(fs.existsSync(path.join(dir, 'cmd/acme/main.go'))).toBe(true); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it('rejects a pre-parsed OpenSDK IR with a friendly error', async () => { + const dir = tmp(); + try { + const ir = path.join(dir, 'ir.json'); + await parseCommand({ spec: SPEC, output: ir }); + await expect(generateCommand({ spec: ir, lang: 'go-cli', output: path.join(dir, 'out') })).rejects.toThrow( + /generate from the OpenAPI document, not a pre-parsed OpenSDK IR/, + ); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it('--dry-run writes nothing', async () => { + const dir = tmp(); + try { + const out = path.join(dir, 'never-created'); + await generateCommand({ spec: SPEC, lang: 'rust-cli', output: out, dryRun: true }); + expect(fs.existsSync(out)).toBe(false); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +}); + +describe('generateTargets partition (sdk.json with SDK + CLI sections)', () => { + it('generates SDK and CLI sections side by side', async () => { + const dir = tmp(); + try { + write(dir, 'sdk.json', { + version: 1, + sdkName: 'petstore', + go: { output: path.join(dir, 'out/go-sdk'), tests: false }, + 'go-cli': { output: path.join(dir, 'out/go-cli'), cliName: 'petstore' }, + }); + const config = await resolveConfig(dir); + expect(config).not.toBeNull(); + await generateTargets({ spec: SPEC, output: path.join(dir, 'out'), config: config!, sdkName: 'petstore' }); + expect(fs.existsSync(path.join(dir, 'out/go-sdk/client.go'))).toBe(true); + expect(fs.existsSync(path.join(dir, 'out/go-cli/cmd/petstore/main.go'))).toBe(true); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it('a CLI-only config generates without SDK sections', async () => { + const dir = tmp(); + try { + write(dir, 'sdk.json', { + version: 1, + 'rust-cli': { output: path.join(dir, 'out/rust-cli'), cliName: 'petstore' }, + }); + const config = await resolveConfig(dir); + await generateTargets({ spec: SPEC, output: path.join(dir, 'out'), config: config! }); + expect(fs.existsSync(path.join(dir, 'out/rust-cli/Cargo.toml'))).toBe(true); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +}); + +describe('publish', () => { + it('skips CLI targets without throwing (even when the dir does not exist)', () => { + expect(() => publishTarget('go-cli', '/nonexistent-cli-dir', {})).not.toThrow(); + expect(() => publishTarget('rust-cli', '/nonexistent-cli-dir', {})).not.toThrow(); + }); +}); + +describe('chain integration (the routing seam the chain engine hits)', () => { + it('a chain with an SDK target and a CLI target generates both', async () => { + const dir = tmp(); + try { + const chainPath = write(dir, 'chain.json', { + version: 1, + sources: { petstore: { inputs: [{ location: SPEC }] } }, + targets: { + 'petstore-node': { + target: 'node', + source: 'petstore', + output: path.join(dir, 'sdk-node'), + options: { tests: false }, + }, + 'petstore-cli': { + target: 'go-cli', + source: 'petstore', + output: path.join(dir, 'cli-go'), + options: { cliName: 'petstore' }, + }, + }, + }); + await runChain({ chain: chainPath, cwd: dir }); + expect(fs.existsSync(path.join(dir, 'sdk-node/package.json'))).toBe(true); + expect(fs.existsSync(path.join(dir, 'cli-go/cmd/petstore/main.go'))).toBe(true); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/packages/xyd-opensdk-cli/__tests__/e2e/cli-chain.test.ts b/packages/xyd-opensdk-cli/__tests__/e2e/cli-chain.test.ts new file mode 100644 index 000000000..c764cfa14 --- /dev/null +++ b/packages/xyd-opensdk-cli/__tests__/e2e/cli-chain.test.ts @@ -0,0 +1,69 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { compileSmoke } from '@xyd-js/opensdk-ci'; + +import { registerBuiltinEmitters, runChain } from '../../src'; + +// Gated chain e2e for CLI OUTPUT targets: `opensdk run` with go-cli + rust-cli +// targets, then each generated project must COMPILE with its real toolchain +// (compileSmoke maps the pseudo-lang to its base toolchain: go-cli → go build, +// rust-cli → cargo build). Runs in the tests-opensdk-pipeline CI job, which +// provisions both toolchains. +const E2E = process.env.E2E_SDK_CHAIN === '1'; + +// The petstore OpenAPI doc vendored as the converter's 1.basic fixture. +const SPEC = path.join(__dirname, '../../../xyd-openapi2opensdk/__fixtures__/1.basic/input.json'); + +describe.runIf(E2E)('chain e2e: CLI output targets compile', () => { + it( + 'opensdk run with go-cli + rust-cli targets → both generated CLIs compile', + async () => { + registerBuiltinEmitters(); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opensdk-cli-chain-')); + try { + const chainPath = path.join(dir, 'chain.json'); + fs.writeFileSync( + chainPath, + JSON.stringify( + { + version: 1, + sources: { petstore: { inputs: [{ location: SPEC }] } }, + targets: { + 'petstore-go-cli': { + target: 'go-cli', + source: 'petstore', + output: path.join(dir, 'cli-go'), + options: { cliName: 'petstore' }, + }, + 'petstore-rust-cli': { + target: 'rust-cli', + source: 'petstore', + output: path.join(dir, 'cli-rust'), + options: { cliName: 'petstore' }, + }, + }, + }, + null, + 2, + ), + ); + await runChain({ chain: chainPath, cwd: dir }); + + expect(fs.existsSync(path.join(dir, 'cli-go/cmd/petstore/main.go'))).toBe(true); + expect(fs.existsSync(path.join(dir, 'cli-rust/src/custom/mod.rs'))).toBe(true); + + // compileSmoke returns false only when the toolchain is absent — the CI + // job provisions both, so treat a skip as a real signal locally too. + expect(compileSmoke('go', path.join(dir, 'cli-go')), 'go toolchain missing?').toBe(true); + expect(compileSmoke('rust', path.join(dir, 'cli-rust')), 'rust toolchain missing?').toBe(true); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }, + 600000, + ); +}); diff --git a/packages/xyd-opensdk-cli/package.json b/packages/xyd-opensdk-cli/package.json index 7983da669..d13293aba 100644 --- a/packages/xyd-opensdk-cli/package.json +++ b/packages/xyd-opensdk-cli/package.json @@ -16,7 +16,11 @@ "format": "bunx biome check --write src" }, "dependencies": { + "@xyd-js/openapi2opencli": "workspace:*", "@xyd-js/openapi2opensdk": "workspace:*", + "@xyd-js/opencli": "workspace:*", + "@xyd-js/opencli2go": "workspace:*", + "@xyd-js/opencli2rust": "workspace:*", "@xyd-js/opensdk-core": "workspace:*", "@xyd-js/opensdk-framework": "workspace:*", "@xyd-js/opensdk-go": "workspace:*", @@ -31,6 +35,7 @@ }, "devDependencies": { "@types/node": "^20.9.0", + "@xyd-js/opensdk-ci": "workspace:*", "rimraf": "^3.0.2", "tsup": "^8.3.0", "typescript": "^5.6.2", diff --git a/packages/xyd-opensdk-cli/src/cli/cli-targets.ts b/packages/xyd-opensdk-cli/src/cli/cli-targets.ts new file mode 100644 index 000000000..fda1deac3 --- /dev/null +++ b/packages/xyd-opensdk-cli/src/cli/cli-targets.ts @@ -0,0 +1,158 @@ +import fs from 'node:fs'; + +import { openapi2opencliFromSource } from '@xyd-js/openapi2opencli'; +import type { OpenApi2OpenCliOptions } from '@xyd-js/openapi2opencli'; +import type { OpencliSpecJson } from '@xyd-js/opencli'; +import { opencli2go } from '@xyd-js/opencli2go'; +import { opencli2rust } from '@xyd-js/opencli2rust'; +import type { OperationHint, PublishTarget } from '@xyd-js/opensdk-core'; +import { type ProjectFileMap, writeProject } from '@xyd-js/opensdk-framework'; + +import { reportWriteResult } from './write-report'; + +/** + * CLI output targets ("go-cli" / "rust-cli"): the OpenCLI pipeline + * (openapi2opencli → opencli2go/rust) surfaced as pseudo-language target ids. + * Routed BEFORE the emitter registry — CLI generation consumes the raw OpenAPI + * document, not the OpenSDK IR, so these are a parallel branch, not Emitters. + * Both backends write through the framework's writeProject, so the regen + * lifecycle (lock, stale-prune, .sdkignore, --merge) applies to CLI outputs too. + */ + +interface CliBackend { + /** Generator name recorded in the .sdk/sdk.lock manifest. */ + generator: string; + /** The backend's own option keys (disjoint from CONVERTER_KEYS — unit-tested). */ + backendKeys: readonly string[]; + generate(spec: OpencliSpecJson, options: Record): ProjectFileMap; +} + +const CLI_BACKENDS: Record = { + 'go-cli': { + generator: 'opencli2go', + backendKeys: ['modulePath', 'binName', 'goVersion', 'baseURL'], + generate: (spec, options) => opencli2go(spec, options), + }, + 'rust-cli': { + generator: 'opencli2rust', + backendKeys: ['crateName', 'binName', 'edition', 'baseURL'], + generate: (spec, options) => opencli2rust(spec, options), + }, +}; + +/** The OpenApi2OpenCliOptions keys accepted in a CLI target's flat option bag. */ +export const CLI_CONVERTER_KEYS = [ + 'cliName', + 'version', + 'grouping', + 'bodyStrategy', + 'includeMethods', + 'includeHeaders', + 'flagCase', + 'actionAliases', + 'verbMap', + 'customActionVerbs', + 'includePaths', + 'maxBodyDepth', + 'authEnvVar', +] as const; + +/** The backend option keys for a CLI target id (exposed for tests). */ +export function cliBackendKeys(lang: string): readonly string[] { + return CLI_BACKENDS[lang.toLowerCase()]?.backendKeys ?? []; +} + +/** Is this --lang / section key / chain target a CLI output target? Exact ids, no aliases. */ +export function isCliTarget(lang: string | undefined): boolean { + return !!lang && lang.toLowerCase() in CLI_BACKENDS; +} + +/** + * Split a CLI target's FLAT option bag (sdk.json section / ChainTarget.options) + * into converter options (openapi2opencli) and backend options (opencli2go/rust). + * The key sets are disjoint, so the split is unambiguous; unknown keys fail loud. + */ +export function splitCliOptions( + lang: string, + bag: Record = {}, +): { converter: OpenApi2OpenCliOptions; backend: Record } { + const def = CLI_BACKENDS[lang.toLowerCase()]; + if (!def) throw new Error(`Not a CLI target: ${lang}`); + const converter: Record = {}; + const backend: Record = {}; + const unknown: string[] = []; + for (const [key, value] of Object.entries(bag)) { + if (value === undefined) continue; + if (key === 'tests') continue; // shared LanguageSection knob; CLI backends emit no self-test suite + if ((CLI_CONVERTER_KEYS as readonly string[]).includes(key)) converter[key] = value; + else if (def.backendKeys.includes(key)) backend[key] = value; + else unknown.push(key); + } + if (unknown.length) { + throw new Error( + `Unknown option(s) for "${lang}": ${unknown.join(', ')}. Valid: ${[...CLI_CONVERTER_KEYS, ...def.backendKeys].join(', ')}`, + ); + } + return { converter: converter as OpenApi2OpenCliOptions, backend }; +} + +export interface CliTargetOptions { + /** OpenAPI spec path (yaml/json). A pre-parsed OpenSDK IR is rejected. */ + spec: string; + /** CLI target id: "go-cli" | "rust-cli". */ + lang: string; + output: string; + /** Defaults the emitted CLI's name (converter `cliName`) when unset. */ + sdkName?: string; + /** The flat option bag (converter + backend keys, split by allowlist). */ + options?: Record; + /** Only `version` is consumed (defaults the converter `version`). */ + publish?: PublishTarget; + /** SDK-tree grouping — meaningless for CLI targets; warned once + ignored. */ + mountRules?: Record; + operationHints?: Record; + /** `--grouping` file path — SDK-tree grouping; warned once + ignored. */ + groupingFile?: string; + dryRun?: boolean; + merge?: boolean; +} + +let warnedSdkGrouping = false; + +/** Generate one CLI output target: openapi2opencli → backend → framework writeProject. */ +export async function generateCliTarget(opts: CliTargetOptions): Promise { + const lang = opts.lang.toLowerCase(); + const backend = CLI_BACKENDS[lang]; + if (!backend) throw new Error(`Not a CLI target: ${opts.lang}`); + + // Mirror loadIR's sniff: a pre-parsed OpenSDK IR cannot feed the OpenCLI pipeline. + if (opts.spec.endsWith('.json')) { + const doc = JSON.parse(fs.readFileSync(opts.spec, 'utf8')); + if (typeof doc.opensdk === 'string') { + throw new Error( + `CLI targets ("${lang}") generate from the OpenAPI document, not a pre-parsed OpenSDK IR. Pass the OpenAPI spec (yaml/json) as --spec.`, + ); + } + } + + // SDK-tree grouping flows into every generateCommand call in mixed configs — + // warn once instead of erroring so mixed SDK+CLI setups keep working. + if ((opts.mountRules || opts.operationHints || opts.groupingFile) && !warnedSdkGrouping) { + warnedSdkGrouping = true; + console.warn(`note: mountRules/operationHints apply to SDK targets only — ignored for "${lang}".`); + } + + const { converter, backend: backendOptions } = splitCliOptions(lang, opts.options); + if (converter.cliName === undefined && opts.sdkName) converter.cliName = opts.sdkName; + if (converter.version === undefined && opts.publish?.version) converter.version = opts.publish.version; + + const cliSpec = await openapi2opencliFromSource(opts.spec, converter); + const files = backend.generate(cliSpec, backendOptions); + + if (opts.dryRun) { + for (const p of Object.keys(files).sort()) console.log(p); + return; + } + const result = await writeProject(files, opts.output, { generator: backend.generator, merge: opts.merge }); + reportWriteResult(Object.keys(files).length, opts.output, result); +} diff --git a/packages/xyd-opensdk-cli/src/cli/generate.ts b/packages/xyd-opensdk-cli/src/cli/generate.ts index 1e99c6511..eef86b169 100644 --- a/packages/xyd-opensdk-cli/src/cli/generate.ts +++ b/packages/xyd-opensdk-cli/src/cli/generate.ts @@ -7,8 +7,10 @@ import { mergeBehaviorOverrides, mergePublishTargets } from '@xyd-js/opensdk-cor import type { OpensdkSpecJson, PublishTarget, SdkBehavior, SdkInfo } from '@xyd-js/opensdk-core'; import { generateFileMap, getEmitter, writeProject } from '@xyd-js/opensdk-framework'; +import { generateCliTarget, isCliTarget } from './cli-targets'; import type { ResolvedConfig } from './config/types'; import { type ConverterInputs, converterOptions } from './grouping'; +import { reportWriteResult } from './write-report'; /** * Override the IR's package identity (`spec.info`) from a merged publish target. @@ -46,21 +48,7 @@ async function emitToDisk( return; } const result = await writeProject(files, output, { merge }); - console.log(`Generated ${Object.keys(files).length} files in ${output}`); - // Merge outcomes (--merge): clean 3-way merges + conflicts to resolve. - for (const rel of result.merged) { - console.log(` ✓ merged your edits into ${rel}`); - } - for (const rel of result.mergeConflicts) { - console.warn(` ⚠ merge conflict in ${rel} — resolve the <<<<<<< markers`); - } - // .sdkignore conflicts + kept-modified orphans: never silently overwritten/lost. - for (const rel of result.conflicts) { - console.warn(` ⚠ .sdkignore: kept your ${rel} — generated output differs (not overwritten)`); - } - for (const rel of result.keptModified) { - console.warn(` ⚠ kept locally-modified ${rel} — no longer generated, not pruned`); - } + reportWriteResult(Object.keys(files).length, output, result); } /** `opensdk generate --lang ` — single target. Behavior is threaded through the converter. */ @@ -80,6 +68,24 @@ export async function generateCommand( merge?: boolean; }, ): Promise { + // CLI output targets (go-cli/rust-cli) branch off BEFORE the OpenSDK IR: they + // consume the raw OpenAPI doc via the OpenCLI pipeline. This single seam also + // covers chain targets — the chain engine calls this same function. + if (isCliTarget(opts.lang)) { + return generateCliTarget({ + spec: opts.spec, + lang: opts.lang, + output: opts.output, + sdkName: opts.sdkName, + options: opts.emitterOptions, + publish: opts.publish, + mountRules: opts.mountRules, + operationHints: opts.operationHints, + groupingFile: opts.grouping, + dryRun: opts.dryRun, + merge: opts.merge, + }); + } const ir = await loadIR(opts.spec, converterOptions(opts)); ir.info = applyPublishIdentity(ir.info, opts.publish); const emitterOptions = opts.noTests @@ -115,11 +121,34 @@ export async function generateTargets( 'No languages declared in the config. Add a language section (e.g. "typescript": { "output": "./sdk/ts" }) or pass --lang.', ); } + + // CLI output targets don't consume the OpenSDK IR — generate them first, and + // only run the converter when SDK languages remain (a CLI-only config must + // not pay for — or fail on — the IR conversion). + const sdkLangs = langs.filter((lang) => !isCliTarget(lang)); + for (const lang of langs.filter(isCliTarget)) { + const target = config.targets?.[lang]; + await generateCliTarget({ + spec: opts.spec, + lang, + output: target?.output ?? path.join(opts.output, lang), + sdkName: opts.sdkName, + options: config.emitterOptions?.[lang], + publish: mergePublishTargets(config.publish, target?.publish), + mountRules: opts.mountRules, + operationHints: opts.operationHints, + groupingFile: opts.grouping, + dryRun: opts.dryRun, + merge: target?.merge ?? opts.merge, + }); + } + if (!sdkLangs.length) return; + const ir = await loadIR(opts.spec, converterOptions({ ...opts, sdk: config.sdk })); // Snapshot the converter's info so each language re-derives identity from a // clean base (a per-language publish never leaks into the next language). const baseInfo = ir.info; - for (const lang of langs) { + for (const lang of sdkLangs) { const target = config.targets?.[lang]; ir.sdk = mergeBehaviorOverrides(config.sdk, target?.behavior) as SdkBehavior; ir.info = applyPublishIdentity(baseInfo, mergePublishTargets(config.publish, target?.publish)); diff --git a/packages/xyd-opensdk-cli/src/cli/index.ts b/packages/xyd-opensdk-cli/src/cli/index.ts index b438eea03..74db341bb 100644 --- a/packages/xyd-opensdk-cli/src/cli/index.ts +++ b/packages/xyd-opensdk-cli/src/cli/index.ts @@ -84,9 +84,14 @@ export async function main(argv: string[] = process.argv): Promise { program .command('generate') - .description('Generate an SDK from an OpenAPI spec (or a pre-parsed IR json). With no --lang, every language declared in sdk.json is generated.') + .description( + 'Generate an SDK from an OpenAPI spec (or a pre-parsed IR json) — or a CLI via the go-cli/rust-cli targets. With no --lang, every language declared in sdk.json is generated.', + ) .requiredOption('--spec ', 'Path to the OpenAPI spec (yaml/json) or OpenSDK IR (.json)') - .option('--lang ', 'Emitter language/alias (go|python|typescript|ruby|java|csharp|rust|...); omit to build every language in sdk.json') + .option( + '--lang ', + 'Emitter language/alias (go|python|typescript|ruby|java|csharp|rust|...) or a CLI target (go-cli|rust-cli); omit to build every language in sdk.json', + ) .option('--output ', 'Output directory (single --lang), or the base dir for per-language subfolders (multi-target)') .option('--sdk-name ', 'SDK name for the converter') .option('--grouping ', 'JSON grouping file ({mountRules, operationHints}); overrides the config values') diff --git a/packages/xyd-opensdk-cli/src/cli/publish.ts b/packages/xyd-opensdk-cli/src/cli/publish.ts index 7f6a1c155..5ac1bb6ae 100644 --- a/packages/xyd-opensdk-cli/src/cli/publish.ts +++ b/packages/xyd-opensdk-cli/src/cli/publish.ts @@ -12,6 +12,7 @@ import { publishPython } from '@xyd-js/opensdk-python'; import { publishRuby } from '@xyd-js/opensdk-ruby'; import { publishRust } from '@xyd-js/opensdk-rust'; +import { isCliTarget } from './cli-targets'; import type { ResolvedConfig } from './config/types'; /** @@ -36,6 +37,14 @@ const PUBLISHERS: Record v /** Package + publish ONE already-generated SDK at `dir` for `lang` via its emitter's publisher. */ export function publishTarget(lang: string, dir: string, opts: EmitterPublishOptions): void { + // CLI outputs have no registry publisher — skip (not error) so a mixed chain + // with --publish never aborts halfway through its SDK targets. + if (isCliTarget(lang)) { + console.warn( + `Skipping publish for "${lang}" — CLI targets have no registry publisher (ship the generated project via its own repo / goreleaser / cargo publish).`, + ); + return; + } const canonical = resolveLanguage(lang); if (!fs.existsSync(dir)) { throw new Error(`No generated SDK at ${dir}. Run \`opensdk generate\` first (or pass --spec to regenerate).`); diff --git a/packages/xyd-opensdk-cli/src/cli/write-report.ts b/packages/xyd-opensdk-cli/src/cli/write-report.ts new file mode 100644 index 000000000..b3c35b4d0 --- /dev/null +++ b/packages/xyd-opensdk-cli/src/cli/write-report.ts @@ -0,0 +1,23 @@ +import type { WriteProjectResult } from '@xyd-js/opensdk-framework'; + +/** + * Report a writeProject outcome: file count + the never-silent buckets (merges, + * conflicts, kept files). Shared by the SDK emit path and the CLI-target path. + */ +export function reportWriteResult(fileCount: number, output: string, result: WriteProjectResult): void { + console.log(`Generated ${fileCount} files in ${output}`); + // Merge outcomes (--merge): clean 3-way merges + conflicts to resolve. + for (const rel of result.merged) { + console.log(` ✓ merged your edits into ${rel}`); + } + for (const rel of result.mergeConflicts) { + console.warn(` ⚠ merge conflict in ${rel} — resolve the <<<<<<< markers`); + } + // .sdkignore conflicts + kept-modified orphans: never silently overwritten/lost. + for (const rel of result.conflicts) { + console.warn(` ⚠ .sdkignore: kept your ${rel} — generated output differs (not overwritten)`); + } + for (const rel of result.keptModified) { + console.warn(` ⚠ kept locally-modified ${rel} — no longer generated, not pruned`); + } +} diff --git a/packages/xyd-opensdk-cli/src/index.ts b/packages/xyd-opensdk-cli/src/index.ts index 9b2f8b4ef..d7e06ddb8 100644 --- a/packages/xyd-opensdk-cli/src/index.ts +++ b/packages/xyd-opensdk-cli/src/index.ts @@ -4,6 +4,10 @@ export { main, registerBuiltinEmitters } from './cli/index'; export { parseCommand } from './cli/parse'; export { generateCommand, generateTargets, loadIR } from './cli/generate'; +// CLI output targets: the OpenCLI pipeline (openapi2opencli → opencli2go/rust) +// surfaced as go-cli / rust-cli pseudo-language target ids. +export { isCliTarget, generateCliTarget, splitCliOptions, cliBackendKeys, CLI_CONVERTER_KEYS } from './cli/cli-targets'; +export type { CliTargetOptions } from './cli/cli-targets'; export { diffCommand } from './cli/diff'; export type { DiffCommandOptions, DiffFailOn } from './cli/diff'; export { publishCommand, publishTarget } from './cli/publish'; diff --git a/packages/xyd-opensdk-schemas/chain.schema.json b/packages/xyd-opensdk-schemas/chain.schema.json index b4eb818b5..719b40ac5 100644 --- a/packages/xyd-opensdk-schemas/chain.schema.json +++ b/packages/xyd-opensdk-schemas/chain.schema.json @@ -96,7 +96,7 @@ "properties": { "target": { "type": "string", - "description": "Emitter language or alias (typescript, go, csharp, ...)." + "description": "Emitter language or alias (typescript, go, csharp, ...) — or a CLI output target (go-cli, rust-cli)." }, "source": { "type": "string", @@ -610,6 +610,263 @@ } }, "additionalProperties": false + }, + "RustSection": { + "type": "object", + "description": "Options for the rust SDK target.", + "properties": { + "output": { + "type": "string", + "description": "Output directory for this language's generated SDK." + }, + "behavior": { + "$ref": "#/$defs/SdkBehavior", + "description": "Per-language behavior, deep-merged over the global `behavior`." + }, + "baseURL": { + "type": "string", + "description": "Default API base URL baked into the runtime (default: the first `servers` entry)." + }, + "tests": { + "type": "boolean", + "description": "Emit the SDK's own test suite (default: true)." + }, + "publish": { + "$ref": "#/$defs/PublishTarget", + "description": "Per-language publish target, merged over the global `publish`." + }, + "packageName": { + "type": "string", + "description": "The Cargo package + lib crate name (snake_case; default: crate_name of info.title)." + }, + "moduleName": { + "type": "string", + "description": "Alias kept for cross-emitter symmetry — same value as the crate/lib name." + }, + "edition": { + "type": "string", + "description": "Rust edition written to Cargo.toml (default: \"2021\")." + } + }, + "additionalProperties": false + }, + "GoCliSection": { + "type": "object", + "description": "Options for the go-cli CLI output target (the OpenCLI pipeline — a command-line tool, not an SDK).", + "properties": { + "output": { + "type": "string", + "description": "Output directory for this target's generated CLI project." + }, + "publish": { + "$ref": "#/$defs/PublishTarget", + "description": "Only `version` is consumed (the emitted CLI version); `opensdk publish` skips CLI targets." + }, + "cliName": { + "type": "string", + "description": "The CLI name (root command; default: the sdkName / spec title)." + }, + "version": { + "type": "string", + "description": "CLI version override (default: the spec info.version; a `publish.version` also flows in)." + }, + "grouping": { + "type": "string", + "enum": [ + "path", + "tag", + "operationId" + ], + "description": "How operations group into the command tree (default: path)." + }, + "bodyStrategy": { + "type": "string", + "enum": [ + "flatten", + "json", + "hybrid" + ], + "description": "Request-body flags: flatten top-level props, one JSON flag, or hybrid (default: flatten)." + }, + "includeMethods": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only these HTTP methods." + }, + "includeHeaders": { + "type": "boolean", + "description": "Emit flags for header parameters (default: false)." + }, + "flagCase": { + "type": "string", + "enum": [ + "kebab", + "camel" + ], + "description": "Flag naming style (default: kebab)." + }, + "actionAliases": { + "type": "boolean", + "description": "Emit action-verb aliases (e.g. `get` for `retrieve`)." + }, + "verbMap": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Override the derived action verbs (listCollection/getItem/createCollection/updateItem/deleteItem)." + }, + "customActionVerbs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Extra trailing-verb path segments treated as actions." + }, + "includePaths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only operations under these path prefixes." + }, + "maxBodyDepth": { + "type": "number", + "description": "Flatten body properties up to this depth; deeper ones become JSON flags." + }, + "authEnvVar": { + "type": "string", + "description": "Env var the generated CLI reads credentials from (default: from the spec security scheme)." + }, + "modulePath": { + "type": "string", + "description": "Go module path (default: example.com/)." + }, + "binName": { + "type": "string", + "description": "Binary name (default: slug of the CLI name)." + }, + "goVersion": { + "type": "string", + "description": "`go` directive in go.mod (default: \"1.22\")." + }, + "baseURL": { + "type": "string", + "description": "Default API base URL baked into the runtime (overridable via _BASE_URL)." + } + }, + "additionalProperties": false + }, + "RustCliSection": { + "type": "object", + "description": "Options for the rust-cli CLI output target (the OpenCLI pipeline — a command-line tool, not an SDK).", + "properties": { + "output": { + "type": "string", + "description": "Output directory for this target's generated CLI project." + }, + "publish": { + "$ref": "#/$defs/PublishTarget", + "description": "Only `version` is consumed (the emitted CLI version); `opensdk publish` skips CLI targets." + }, + "cliName": { + "type": "string", + "description": "The CLI name (root command; default: the sdkName / spec title)." + }, + "version": { + "type": "string", + "description": "CLI version override (default: the spec info.version; a `publish.version` also flows in)." + }, + "grouping": { + "type": "string", + "enum": [ + "path", + "tag", + "operationId" + ], + "description": "How operations group into the command tree (default: path)." + }, + "bodyStrategy": { + "type": "string", + "enum": [ + "flatten", + "json", + "hybrid" + ], + "description": "Request-body flags: flatten top-level props, one JSON flag, or hybrid (default: flatten)." + }, + "includeMethods": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only these HTTP methods." + }, + "includeHeaders": { + "type": "boolean", + "description": "Emit flags for header parameters (default: false)." + }, + "flagCase": { + "type": "string", + "enum": [ + "kebab", + "camel" + ], + "description": "Flag naming style (default: kebab)." + }, + "actionAliases": { + "type": "boolean", + "description": "Emit action-verb aliases (e.g. `get` for `retrieve`)." + }, + "verbMap": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Override the derived action verbs (listCollection/getItem/createCollection/updateItem/deleteItem)." + }, + "customActionVerbs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Extra trailing-verb path segments treated as actions." + }, + "includePaths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only operations under these path prefixes." + }, + "maxBodyDepth": { + "type": "number", + "description": "Flatten body properties up to this depth; deeper ones become JSON flags." + }, + "authEnvVar": { + "type": "string", + "description": "Env var the generated CLI reads credentials from (default: from the spec security scheme)." + }, + "crateName": { + "type": "string", + "description": "Cargo package name (default: crate_name of the CLI name)." + }, + "binName": { + "type": "string", + "description": "Binary name (default: slug of the CLI name)." + }, + "edition": { + "type": "string", + "description": "Rust edition in Cargo.toml (default: \"2021\")." + }, + "baseURL": { + "type": "string", + "description": "Default API base URL baked into the runtime (overridable via _BASE_URL)." + } + }, + "additionalProperties": false } } } diff --git a/packages/xyd-opensdk-schemas/scripts/gen-schema.mjs b/packages/xyd-opensdk-schemas/scripts/gen-schema.mjs index 4988ccbeb..c01be7b3c 100644 --- a/packages/xyd-opensdk-schemas/scripts/gen-schema.mjs +++ b/packages/xyd-opensdk-schemas/scripts/gen-schema.mjs @@ -104,6 +104,15 @@ const LANG_OPTIONS = { targetFramework: str('The .csproj target framework moniker (default: "net8.0").'), }, }, + // packages/xyd-opensdk-rust/src/types.ts + rust: { + aliases: ['rust', 'rs'], + options: { + packageName: str('The Cargo package + lib crate name (snake_case; default: crate_name of info.title).'), + moduleName: str('Alias kept for cross-emitter symmetry — same value as the crate/lib name.'), + edition: str('Rust edition written to Cargo.toml (default: "2021").'), + }, + }, }; // Shared fields on every language section (from the emitters' common options). @@ -128,6 +137,79 @@ for (const [lang, { aliases, options }] of Object.entries(LANG_OPTIONS)) { patternProperties[pattern] = { $ref: `#/$defs/${defName}` }; } +// ── CLI OUTPUT targets — the OpenCLI pipeline (openapi2opencli → opencli2go / +// opencli2rust) surfaced as go-cli / rust-cli target ids. A section is one FLAT +// bag: converter keys (shared) + that backend's own keys; the CLI splits them by +// allowlist (packages/xyd-opensdk-cli/src/cli/cli-targets.ts — keep in sync). ── +const CLI_CONVERTER_PROPS = { + cliName: str('The CLI name (root command; default: the sdkName / spec title).'), + version: str('CLI version override (default: the spec info.version; a `publish.version` also flows in).'), + grouping: { + type: 'string', + enum: ['path', 'tag', 'operationId'], + description: 'How operations group into the command tree (default: path).', + }, + bodyStrategy: { + type: 'string', + enum: ['flatten', 'json', 'hybrid'], + description: 'Request-body flags: flatten top-level props, one JSON flag, or hybrid (default: flatten).', + }, + includeMethods: { type: 'array', items: { type: 'string' }, description: 'Only these HTTP methods.' }, + includeHeaders: { type: 'boolean', description: 'Emit flags for header parameters (default: false).' }, + flagCase: { type: 'string', enum: ['kebab', 'camel'], description: 'Flag naming style (default: kebab).' }, + actionAliases: { type: 'boolean', description: 'Emit action-verb aliases (e.g. `get` for `retrieve`).' }, + verbMap: { + type: 'object', + additionalProperties: { type: 'string' }, + description: 'Override the derived action verbs (listCollection/getItem/createCollection/updateItem/deleteItem).', + }, + customActionVerbs: { type: 'array', items: { type: 'string' }, description: 'Extra trailing-verb path segments treated as actions.' }, + includePaths: { type: 'array', items: { type: 'string' }, description: 'Only operations under these path prefixes.' }, + maxBodyDepth: { type: 'number', description: 'Flatten body properties up to this depth; deeper ones become JSON flags.' }, + authEnvVar: str('Env var the generated CLI reads credentials from (default: from the spec security scheme).'), +}; +const CLI_LANG_OPTIONS = { + // packages/xyd-opencli2go/src/types.ts + 'go-cli': { + options: { + modulePath: str('Go module path (default: example.com/).'), + binName: str('Binary name (default: slug of the CLI name).'), + goVersion: str('`go` directive in go.mod (default: "1.22").'), + baseURL: str('Default API base URL baked into the runtime (overridable via _BASE_URL).'), + }, + }, + // packages/xyd-opencli2rust/src/types.ts + 'rust-cli': { + options: { + crateName: str('Cargo package name (default: crate_name of the CLI name).'), + binName: str('Binary name (default: slug of the CLI name).'), + edition: str('Rust edition in Cargo.toml (default: "2021").'), + baseURL: str('Default API base URL baked into the runtime (overridable via _BASE_URL).'), + }, + }, +}; +for (const [lang, { options }] of Object.entries(CLI_LANG_OPTIONS)) { + const defName = `${lang + .split('-') + .map((w) => w[0].toUpperCase() + w.slice(1)) + .join('')}Section`; + $defs[defName] = { + type: 'object', + description: `Options for the ${lang} CLI output target (the OpenCLI pipeline — a command-line tool, not an SDK).`, + properties: { + output: str("Output directory for this target's generated CLI project."), + publish: { + $ref: '#/$defs/PublishTarget', + description: 'Only `version` is consumed (the emitted CLI version); `opensdk publish` skips CLI targets.', + }, + ...CLI_CONVERTER_PROPS, + ...options, + }, + additionalProperties: false, + }; + patternProperties[`^(${lang})$`] = { $ref: `#/$defs/${defName}` }; +} + const schema = { $schema: 'https://json-schema.org/draft/2020-12/schema', $id: 'https://unpkg.com/@xyd-js/opensdk-cli/sdk.schema.json', @@ -163,7 +245,7 @@ const schema = { writeFileSync(outPath, `${JSON.stringify(schema, null, 2)}\n`); console.log( - `Wrote ${outPath} (${BEHAVIOR_DEFS.length} behavior defs + ${Object.keys(LANG_OPTIONS).length} language sections)`, + `Wrote ${outPath} (${BEHAVIOR_DEFS.length} behavior defs + ${Object.keys(LANG_OPTIONS).length} language sections + ${Object.keys(CLI_LANG_OPTIONS).length} CLI target sections)`, ); // ── chain.schema.json — the `opensdk run` pipeline config (sources → targets), @@ -216,7 +298,7 @@ const chainSchema = { type: 'object', required: ['target', 'source'], properties: { - target: str('Emitter language or alias (typescript, go, csharp, ...).'), + target: str('Emitter language or alias (typescript, go, csharp, ...) — or a CLI output target (go-cli, rust-cli).'), source: str('Name of the `sources` entry this target generates from.'), output: str('SDK output directory (default ./sdk/).'), sdkName: str('SDK name passed to the converter.'), diff --git a/packages/xyd-opensdk-schemas/sdk.schema.json b/packages/xyd-opensdk-schemas/sdk.schema.json index 145712dbf..11953c090 100644 --- a/packages/xyd-opensdk-schemas/sdk.schema.json +++ b/packages/xyd-opensdk-schemas/sdk.schema.json @@ -74,6 +74,15 @@ }, "^(csharp|cs|c#|dotnet|\\.net)$": { "$ref": "#/$defs/DotnetSection" + }, + "^(rust|rs)$": { + "$ref": "#/$defs/RustSection" + }, + "^(go-cli)$": { + "$ref": "#/$defs/GoCliSection" + }, + "^(rust-cli)$": { + "$ref": "#/$defs/RustCliSection" } }, "additionalProperties": true, @@ -537,6 +546,263 @@ } }, "additionalProperties": false + }, + "RustSection": { + "type": "object", + "description": "Options for the rust SDK target.", + "properties": { + "output": { + "type": "string", + "description": "Output directory for this language's generated SDK." + }, + "behavior": { + "$ref": "#/$defs/SdkBehavior", + "description": "Per-language behavior, deep-merged over the global `behavior`." + }, + "baseURL": { + "type": "string", + "description": "Default API base URL baked into the runtime (default: the first `servers` entry)." + }, + "tests": { + "type": "boolean", + "description": "Emit the SDK's own test suite (default: true)." + }, + "publish": { + "$ref": "#/$defs/PublishTarget", + "description": "Per-language publish target, merged over the global `publish`." + }, + "packageName": { + "type": "string", + "description": "The Cargo package + lib crate name (snake_case; default: crate_name of info.title)." + }, + "moduleName": { + "type": "string", + "description": "Alias kept for cross-emitter symmetry — same value as the crate/lib name." + }, + "edition": { + "type": "string", + "description": "Rust edition written to Cargo.toml (default: \"2021\")." + } + }, + "additionalProperties": false + }, + "GoCliSection": { + "type": "object", + "description": "Options for the go-cli CLI output target (the OpenCLI pipeline — a command-line tool, not an SDK).", + "properties": { + "output": { + "type": "string", + "description": "Output directory for this target's generated CLI project." + }, + "publish": { + "$ref": "#/$defs/PublishTarget", + "description": "Only `version` is consumed (the emitted CLI version); `opensdk publish` skips CLI targets." + }, + "cliName": { + "type": "string", + "description": "The CLI name (root command; default: the sdkName / spec title)." + }, + "version": { + "type": "string", + "description": "CLI version override (default: the spec info.version; a `publish.version` also flows in)." + }, + "grouping": { + "type": "string", + "enum": [ + "path", + "tag", + "operationId" + ], + "description": "How operations group into the command tree (default: path)." + }, + "bodyStrategy": { + "type": "string", + "enum": [ + "flatten", + "json", + "hybrid" + ], + "description": "Request-body flags: flatten top-level props, one JSON flag, or hybrid (default: flatten)." + }, + "includeMethods": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only these HTTP methods." + }, + "includeHeaders": { + "type": "boolean", + "description": "Emit flags for header parameters (default: false)." + }, + "flagCase": { + "type": "string", + "enum": [ + "kebab", + "camel" + ], + "description": "Flag naming style (default: kebab)." + }, + "actionAliases": { + "type": "boolean", + "description": "Emit action-verb aliases (e.g. `get` for `retrieve`)." + }, + "verbMap": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Override the derived action verbs (listCollection/getItem/createCollection/updateItem/deleteItem)." + }, + "customActionVerbs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Extra trailing-verb path segments treated as actions." + }, + "includePaths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only operations under these path prefixes." + }, + "maxBodyDepth": { + "type": "number", + "description": "Flatten body properties up to this depth; deeper ones become JSON flags." + }, + "authEnvVar": { + "type": "string", + "description": "Env var the generated CLI reads credentials from (default: from the spec security scheme)." + }, + "modulePath": { + "type": "string", + "description": "Go module path (default: example.com/)." + }, + "binName": { + "type": "string", + "description": "Binary name (default: slug of the CLI name)." + }, + "goVersion": { + "type": "string", + "description": "`go` directive in go.mod (default: \"1.22\")." + }, + "baseURL": { + "type": "string", + "description": "Default API base URL baked into the runtime (overridable via _BASE_URL)." + } + }, + "additionalProperties": false + }, + "RustCliSection": { + "type": "object", + "description": "Options for the rust-cli CLI output target (the OpenCLI pipeline — a command-line tool, not an SDK).", + "properties": { + "output": { + "type": "string", + "description": "Output directory for this target's generated CLI project." + }, + "publish": { + "$ref": "#/$defs/PublishTarget", + "description": "Only `version` is consumed (the emitted CLI version); `opensdk publish` skips CLI targets." + }, + "cliName": { + "type": "string", + "description": "The CLI name (root command; default: the sdkName / spec title)." + }, + "version": { + "type": "string", + "description": "CLI version override (default: the spec info.version; a `publish.version` also flows in)." + }, + "grouping": { + "type": "string", + "enum": [ + "path", + "tag", + "operationId" + ], + "description": "How operations group into the command tree (default: path)." + }, + "bodyStrategy": { + "type": "string", + "enum": [ + "flatten", + "json", + "hybrid" + ], + "description": "Request-body flags: flatten top-level props, one JSON flag, or hybrid (default: flatten)." + }, + "includeMethods": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only these HTTP methods." + }, + "includeHeaders": { + "type": "boolean", + "description": "Emit flags for header parameters (default: false)." + }, + "flagCase": { + "type": "string", + "enum": [ + "kebab", + "camel" + ], + "description": "Flag naming style (default: kebab)." + }, + "actionAliases": { + "type": "boolean", + "description": "Emit action-verb aliases (e.g. `get` for `retrieve`)." + }, + "verbMap": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Override the derived action verbs (listCollection/getItem/createCollection/updateItem/deleteItem)." + }, + "customActionVerbs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Extra trailing-verb path segments treated as actions." + }, + "includePaths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only operations under these path prefixes." + }, + "maxBodyDepth": { + "type": "number", + "description": "Flatten body properties up to this depth; deeper ones become JSON flags." + }, + "authEnvVar": { + "type": "string", + "description": "Env var the generated CLI reads credentials from (default: from the spec security scheme)." + }, + "crateName": { + "type": "string", + "description": "Cargo package name (default: crate_name of the CLI name)." + }, + "binName": { + "type": "string", + "description": "Binary name (default: slug of the CLI name)." + }, + "edition": { + "type": "string", + "description": "Rust edition in Cargo.toml (default: \"2021\")." + }, + "baseURL": { + "type": "string", + "description": "Default API base URL baked into the runtime (overridable via _BASE_URL)." + } + }, + "additionalProperties": false } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3dcd6c5ea..e3c2a6ff4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1750,6 +1750,31 @@ importers: specifier: ^2.1.1 version: 2.1.9(@types/node@20.9.0)(happy-dom@15.11.7)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1) + packages/xyd-opencli2rust: + dependencies: + '@xyd-js/opencli': + specifier: workspace:* + version: link:../xyd-opencli + '@xyd-js/opensdk-framework': + specifier: workspace:* + version: link:../xyd-opensdk-framework + devDependencies: + '@types/node': + specifier: ^20.9.0 + version: 20.9.0 + rimraf: + specifier: ^3.0.2 + version: 3.0.2 + tsup: + specifier: ^8.3.0 + version: 8.5.1(@swc/core@1.15.33)(jiti@2.7.0)(postcss@8.5.14)(typescript@5.8.3)(yaml@2.8.4) + typescript: + specifier: ^5.6.2 + version: 5.8.3 + vitest: + specifier: ^2.1.1 + version: 2.1.9(@types/node@20.9.0)(happy-dom@15.11.7)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1) + packages/xyd-opensdk-chain: dependencies: '@xyd-js/opensdk-core': @@ -1841,9 +1866,21 @@ importers: packages/xyd-opensdk-cli: dependencies: + '@xyd-js/openapi2opencli': + specifier: workspace:* + version: link:../xyd-openapi2opencli '@xyd-js/openapi2opensdk': specifier: workspace:* version: link:../xyd-openapi2opensdk + '@xyd-js/opencli': + specifier: workspace:* + version: link:../xyd-opencli + '@xyd-js/opencli2go': + specifier: workspace:* + version: link:../xyd-opencli2go + '@xyd-js/opencli2rust': + specifier: workspace:* + version: link:../xyd-opencli2rust '@xyd-js/opensdk-chain': specifier: workspace:* version: link:../xyd-opensdk-chain @@ -1881,6 +1918,9 @@ importers: '@types/node': specifier: ^20.9.0 version: 20.9.0 + '@xyd-js/opensdk-ci': + specifier: workspace:* + version: link:../xyd-opensdk-ci rimraf: specifier: ^3.0.2 version: 3.0.2 @@ -24843,7 +24883,7 @@ snapshots: '@fastify/error': 3.4.1 fastq: 1.20.1 - axios@1.15.2(debug@4.3.4): + axios@1.15.2: dependencies: follow-redirects: 1.16.0(debug@4.3.7) form-data: 4.0.5 @@ -24851,6 +24891,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.15.2(debug@4.3.4): + dependencies: + follow-redirects: 1.16.0(debug@4.3.4) + form-data: 4.0.5 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + axios@1.16.0: dependencies: follow-redirects: 1.16.0(debug@4.3.7) @@ -27387,6 +27435,10 @@ snapshots: dependencies: from2: 2.3.0 + follow-redirects@1.16.0(debug@4.3.4): + optionalDependencies: + debug: 4.3.4 + follow-redirects@1.16.0(debug@4.3.7): optionalDependencies: debug: 4.3.7(supports-color@9.4.0) @@ -28266,7 +28318,7 @@ snapshots: jsonwebtoken: 9.0.3 load-esm: 1.0.3 mime-types: 2.1.35 - retry-axios: 2.6.0(axios@1.15.2(debug@4.3.4)) + retry-axios: 2.6.0(axios@1.15.2) tough-cookie: 4.1.3 transitivePeerDependencies: - supports-color @@ -32581,9 +32633,9 @@ snapshots: ret@0.4.3: {} - retry-axios@2.6.0(axios@1.15.2(debug@4.3.4)): + retry-axios@2.6.0(axios@1.15.2): dependencies: - axios: 1.15.2(debug@4.3.4) + axios: 1.15.2 retry@0.12.0: {} From c8f67602b8944e904da165ccb8c82f6966fc9db9 Mon Sep 17 00:00:00 2001 From: Patryk Zdunowski Date: Sun, 2 Aug 2026 02:18:14 +0200 Subject: [PATCH 3/5] feat(apitoolchain): generate the apitoolchain Rust CLI via the rust-cli chain target --- packages/apitoolchain-api-cli/.gitignore | 1 + packages/apitoolchain-api-cli/.sdk/sdk.lock | 36 + packages/apitoolchain-api-cli/Cargo.lock | 1381 +++++++++++++++++ packages/apitoolchain-api-cli/Cargo.toml | 17 + .../apitoolchain-api-cli/src/custom/mod.rs | 29 + packages/apitoolchain-api-cli/src/gen/cli.rs | 86 + .../src/gen/cmd/api_keys.rs | 102 ++ .../apitoolchain-api-cli/src/gen/cmd/apis.rs | 281 ++++ .../apitoolchain-api-cli/src/gen/cmd/auth.rs | 172 ++ .../src/gen/cmd/context.rs | 122 ++ .../src/gen/cmd/docs_projects.rs | 101 ++ .../src/gen/cmd/git_providers.rs | 199 +++ .../src/gen/cmd/mcp_servers.rs | 108 ++ .../src/gen/cmd/members.rs | 153 ++ .../apitoolchain-api-cli/src/gen/cmd/mod.rs | 20 + .../src/gen/cmd/notifications.rs | 93 ++ .../src/gen/cmd/overview.rs | 47 + .../src/gen/cmd/package_registries.rs | 127 ++ .../src/gen/cmd/projects.rs | 141 ++ .../src/gen/cmd/registry_connections.rs | 165 ++ .../src/gen/cmd/releases.rs | 151 ++ .../src/gen/cmd/repo_connections.rs | 337 ++++ .../src/gen/cmd/sdk_targets.rs | 105 ++ .../apitoolchain-api-cli/src/gen/cmd/sdks.rs | 386 +++++ .../apitoolchain-api-cli/src/gen/cmd/usage.rs | 53 + packages/apitoolchain-api-cli/src/gen/mod.rs | 5 + .../src/gen/runtime/config.rs | 18 + .../src/gen/runtime/custom.rs | 103 ++ .../src/gen/runtime/http.rs | 50 + .../src/gen/runtime/mod.rs | 103 ++ .../src/gen/runtime/overrides.rs | 51 + packages/apitoolchain-api-cli/src/main.rs | 11 + packages/apitoolchain-api-node/src/models.ts | 27 + .../src/resources/sdks.ts | 1 + packages/apitoolchain-sdk-chain/chain.json | 6 + 35 files changed, 4788 insertions(+) create mode 100644 packages/apitoolchain-api-cli/.gitignore create mode 100644 packages/apitoolchain-api-cli/.sdk/sdk.lock create mode 100644 packages/apitoolchain-api-cli/Cargo.lock create mode 100644 packages/apitoolchain-api-cli/Cargo.toml create mode 100644 packages/apitoolchain-api-cli/src/custom/mod.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cli.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/api_keys.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/apis.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/auth.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/context.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/docs_projects.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/git_providers.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/mcp_servers.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/members.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/mod.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/notifications.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/overview.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/package_registries.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/projects.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/registry_connections.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/releases.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/repo_connections.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/sdk_targets.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/sdks.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/cmd/usage.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/mod.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/runtime/config.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/runtime/custom.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/runtime/http.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/runtime/mod.rs create mode 100644 packages/apitoolchain-api-cli/src/gen/runtime/overrides.rs create mode 100644 packages/apitoolchain-api-cli/src/main.rs diff --git a/packages/apitoolchain-api-cli/.gitignore b/packages/apitoolchain-api-cli/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/packages/apitoolchain-api-cli/.gitignore @@ -0,0 +1 @@ +/target diff --git a/packages/apitoolchain-api-cli/.sdk/sdk.lock b/packages/apitoolchain-api-cli/.sdk/sdk.lock new file mode 100644 index 000000000..9e5270126 --- /dev/null +++ b/packages/apitoolchain-api-cli/.sdk/sdk.lock @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "generator": "opencli2rust", + "files": { + ".gitignore": "44c92e3a70ad3307b7056871c2bdb096d8bfa9373f5bf06a79bb6324a20ff2fb", + "Cargo.toml": "bd3658b3663276fba4f396d94571ca0d6cb156a4e60b2d40faadf30a5a8b8138", + "src/custom/mod.rs": "17f6cce7c7b000e9aa9fdfc31bb45054fd681ff1100280881c2fad97713aaa0b", + "src/gen/cli.rs": "afda3694c044e340fdb2b02256acc30fef4095835ebc59fb701557e4f8df5358", + "src/gen/cmd/api_keys.rs": "d38b175d2e387e4785ad2b89387a39e65590aeaa3c88ec9f4cb4e34cc801144e", + "src/gen/cmd/apis.rs": "892b8248348fc22614251ce01ed09b4b4bd4bf9b57765e68d00e38fe1c5f73d3", + "src/gen/cmd/auth.rs": "f818c09baeff416656a9f59b5787cdeaaa928a8b9824968420e7db35437732e0", + "src/gen/cmd/context.rs": "a58b7383e3b95bdc939e44aed4d88b8929c01704de402cd11affb07b15f086fe", + "src/gen/cmd/docs_projects.rs": "503c863177899fa682951174a7bdd59363088d6c36e75a003d3add9bedeac907", + "src/gen/cmd/git_providers.rs": "0fcc4eea0ec1288679baede7fa9127cde2761bdb7bd5119b021bfab578594460", + "src/gen/cmd/mcp_servers.rs": "315a5433836f2150554647855699dab5fcaaf1f5be11a61a39517574d86bdda4", + "src/gen/cmd/members.rs": "ee8f734b68688fb1cdbfce7fa3fa9e9707224d642368590973303950c0ccb51e", + "src/gen/cmd/mod.rs": "7a9935975bd74d14ef92c0e09942998baf7a0ecdddd614863dc825e28d6cf344", + "src/gen/cmd/notifications.rs": "a82f6217ab148509ad3db133861da05a2e1c1bdd68ede55c34116f383f4e005d", + "src/gen/cmd/overview.rs": "b9a2660eaa0e164d0c4d9751071ff699c43bb6df79572348591d9055e6bcc5a6", + "src/gen/cmd/package_registries.rs": "1ba3e04dd65c9d0feec13ed6389071d9ba2f16f4bce7ac3d6f7ae0ae5030dbf6", + "src/gen/cmd/projects.rs": "f7c2810ac568bd54dcfef8c38ce8f90ba860d926e98b7966b336f8fb66838138", + "src/gen/cmd/registry_connections.rs": "18ece2aa582bed18614259a1d1187f22de0bd32368b6ed988e027fa95b72e62e", + "src/gen/cmd/releases.rs": "b4baff21d09a91726899c9b76784067d8e69226329d634ced37203053aa44ffc", + "src/gen/cmd/repo_connections.rs": "ca2bb2f43b914a338aab67170008980cf1415fa23d527a08cdf3a38cf9b9c5ce", + "src/gen/cmd/sdk_targets.rs": "4cd1d858fdeaf170bd7353bb31e3fea3c290e6a5764c1bfc9ba8dd20182e6400", + "src/gen/cmd/sdks.rs": "dd831b3c2a5626fa502b566ae50ba5eb384d5d50a4a78040fc1f5460bd757d78", + "src/gen/cmd/usage.rs": "9d1b1e9fc9bf9996b2293ca84e18cf07cb930290f4040b71da01e0be182372ca", + "src/gen/mod.rs": "3ffa1def7e7adff0d7b1d9db5531f0279adc6be59d5f41be33ca3fc08935ffc6", + "src/gen/runtime/config.rs": "a17c5fdc587a8c8c5d4b7602d8aebb242ec24d91368ad46a50defdd11789ec2c", + "src/gen/runtime/custom.rs": "64f7807ceff5f05d440ef49c31ed097a6302c804ebf501600b85bc316e97fe54", + "src/gen/runtime/http.rs": "8d69331571c3ca676567398b1931f48afb461cabd47e7fcf82bd0417bbc46ca0", + "src/gen/runtime/mod.rs": "1e14f1f5fc02b664fa9932a828f4ba19f47c0b2f8d37b55925c063445ee2ebbc", + "src/gen/runtime/overrides.rs": "e9a5ee574239b1d64de9ee11db71ba732d82894d64834bfdb619a1d8029d2c22", + "src/main.rs": "d671f2a0b3ec7fc2cd119ed4c34d17e21906b06abc589a1c5b41283abc8f4376" + } +} diff --git a/packages/apitoolchain-api-cli/Cargo.lock b/packages/apitoolchain-api-cli/Cargo.lock new file mode 100644 index 000000000..05f5b3712 --- /dev/null +++ b/packages/apitoolchain-api-cli/Cargo.lock @@ -0,0 +1,1381 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "apitoolchain" +version = "0.0.0" +dependencies = [ + "clap", + "percent-encoding", + "reqwest", + "serde_json", + "tokio", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "clap" +version = "4.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/packages/apitoolchain-api-cli/Cargo.toml b/packages/apitoolchain-api-cli/Cargo.toml new file mode 100644 index 000000000..3043c4e14 --- /dev/null +++ b/packages/apitoolchain-api-cli/Cargo.toml @@ -0,0 +1,17 @@ +# Generated once by opencli2rust — yours to edit; regeneration never overwrites it. + +[package] +name = "apitoolchain" +version = "0.0.0" +edition = "2021" + +[dependencies] +clap = { version = "4", features = ["string"] } +percent-encoding = "2" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +serde_json = "1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } + +[[bin]] +name = "apitoolchain" +path = "src/main.rs" diff --git a/packages/apitoolchain-api-cli/src/custom/mod.rs b/packages/apitoolchain-api-cli/src/custom/mod.rs new file mode 100644 index 000000000..f6944805d --- /dev/null +++ b/packages/apitoolchain-api-cli/src/custom/mod.rs @@ -0,0 +1,29 @@ +//! Yours — scaffolded once by opencli2rust, never overwritten by regeneration. +//! +//! Two extension seams, both optional: +//! * `CliOverrides` on [`Custom`] — hook/override every generated command +//! (see src/gen/runtime/overrides.rs for the available methods). +//! * [`register`] — add fully custom commands anywhere in the tree; a command +//! registered on an existing path takes over that command's behavior. + +use crate::gen::runtime::{CliOverrides, CustomCommands}; + +/// Override only what you need — every trait method has a default. +pub struct Custom; + +impl CliOverrides for Custom {} + +/// Register hand-written commands. Called by the generated `main`. +pub fn register(commands: &mut CustomCommands) { + let _ = commands; + // Example — a fully custom `tools hello` subcommand: + // + // commands.add(&["tools"], clap::Command::new("hello").about("Say hello"), |_ctx, _m| async move { + // println!("hello"); + // Ok(()) + // }); +} + +pub fn overrides() -> Custom { + Custom +} diff --git a/packages/apitoolchain-api-cli/src/gen/cli.rs b/packages/apitoolchain-api-cli/src/gen/cli.rs new file mode 100644 index 000000000..7c905d638 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cli.rs @@ -0,0 +1,86 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::ArgMatches; + +use super::cmd; +use super::runtime::{CliOverrides, Context, CustomCommands}; + +/// The generated root command tree. +pub fn root_command() -> clap::Command { + clap::Command::new("apitoolchain") + .version("0.0.0") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(cmd::api_keys::command()) + .subcommand(cmd::apis::command()) + .subcommand(cmd::auth::command()) + .subcommand(cmd::context::command()) + .subcommand(cmd::docs_projects::command()) + .subcommand(cmd::git_providers::command()) + .subcommand(cmd::mcp_servers::command()) + .subcommand(cmd::members::command()) + .subcommand(cmd::notifications::command()) + .subcommand(cmd::overview::command()) + .subcommand(cmd::package_registries::command()) + .subcommand(cmd::projects::command()) + .subcommand(cmd::registry_connections::command()) + .subcommand(cmd::releases::command()) + .subcommand(cmd::repo_connections::command()) + .subcommand(cmd::sdk_targets::command()) + .subcommand(cmd::sdks::command()) + .subcommand(cmd::usage::command()) +} + +/// Parse args, resolve the invoked command path, and dispatch — custom commands +/// first (so user registrations can override generated behavior), then the +/// generated handlers. +pub async fn run(o: O, customs: CustomCommands) -> ExitCode { + let matches = customs.graft(root_command()).get_matches(); + let ctx = Context::from_env(); + let (cmd_path, leaf) = descend(&matches); + + if let Some(handler) = customs.find(&cmd_path) { + return match handler(ctx.clone(), leaf.clone()).await { + Ok(()) => ExitCode::SUCCESS, + Err(err) => o.print_error(&cmd_path, &err), + }; + } + + match cmd_path.first().map(String::as_str) { + Some("api-keys") => cmd::api_keys::run(&ctx, &o, &cmd_path, leaf).await, + Some("apis") => cmd::apis::run(&ctx, &o, &cmd_path, leaf).await, + Some("auth") => cmd::auth::run(&ctx, &o, &cmd_path, leaf).await, + Some("context") => cmd::context::run(&ctx, &o, &cmd_path, leaf).await, + Some("docs-projects") => cmd::docs_projects::run(&ctx, &o, &cmd_path, leaf).await, + Some("git-providers") => cmd::git_providers::run(&ctx, &o, &cmd_path, leaf).await, + Some("mcp-servers") => cmd::mcp_servers::run(&ctx, &o, &cmd_path, leaf).await, + Some("members") => cmd::members::run(&ctx, &o, &cmd_path, leaf).await, + Some("notifications") => cmd::notifications::run(&ctx, &o, &cmd_path, leaf).await, + Some("overview") => cmd::overview::run(&ctx, &o, &cmd_path, leaf).await, + Some("package-registries") => cmd::package_registries::run(&ctx, &o, &cmd_path, leaf).await, + Some("projects") => cmd::projects::run(&ctx, &o, &cmd_path, leaf).await, + Some("registry-connections") => cmd::registry_connections::run(&ctx, &o, &cmd_path, leaf).await, + Some("releases") => cmd::releases::run(&ctx, &o, &cmd_path, leaf).await, + Some("repo-connections") => cmd::repo_connections::run(&ctx, &o, &cmd_path, leaf).await, + Some("sdk-targets") => cmd::sdk_targets::run(&ctx, &o, &cmd_path, leaf).await, + Some("sdks") => cmd::sdks::run(&ctx, &o, &cmd_path, leaf).await, + Some("usage") => cmd::usage::run(&ctx, &o, &cmd_path, leaf).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +/// Walk matches to the invoked leaf, collecting the full command path. +fn descend(matches: &ArgMatches) -> (Vec, &ArgMatches) { + let mut path = Vec::new(); + let mut current = matches; + while let Some((name, sub)) = current.subcommand() { + path.push(name.to_string()); + current = sub; + } + (path, current) +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/api_keys.rs b/packages/apitoolchain-api-cli/src/gen/cmd/api_keys.rs new file mode 100644 index 000000000..41a65cb97 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/api_keys.rs @@ -0,0 +1,102 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("api-keys") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("list")) + .subcommand( + Command::new("create") + .arg( + Arg::new("name") + .long("name") + .required(true), + ), + ) + .subcommand( + Command::new("delete") + .arg( + Arg::new("id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["api-keys", "list"] => handle_api_keys_list(ctx, o, cmd_path, m).await, + ["api-keys", "create"] => handle_api_keys_create(ctx, o, cmd_path, m).await, + ["api-keys", "delete"] => handle_api_keys_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_api_keys_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/api-keys".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_api_keys_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/api-keys".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_api_keys_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/api-keys/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/apis.rs b/packages/apitoolchain-api-cli/src/gen/cmd/apis.rs new file mode 100644 index 000000000..4d97ebe31 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/apis.rs @@ -0,0 +1,281 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("apis") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("dist-tags") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Proxy: create or move a dist-tag (`latest`, `canary`, …) to a version.") + .arg( + Arg::new("api-id") + .required(true), + ) + .arg( + Arg::new("tag") + .long("tag") + .required(true), + ) + .arg( + Arg::new("version") + .long("version") + .required(true), + ), + ), + ) + .subcommand(Command::new("list")) + .subcommand( + Command::new("create") + .arg( + Arg::new("name") + .long("name") + .required(true), + ) + .arg( + Arg::new("id") + .long("id") + .help("Explicit id/slug for the entry. Slugified server-side; defaults to a slug\nof `name`. Lets the display title (`name`) be decoupled from the stable id."), + ) + .arg( + Arg::new("format") + .long("format"), + ) + .arg( + Arg::new("kind") + .long("kind") + .help("`api` (default) or `schema` — classifies the uploaded document."), + ) + .arg( + Arg::new("ns") + .long("ns"), + ) + .arg( + Arg::new("source") + .long("source"), + ) + .arg( + Arg::new("spec-text") + .long("spec-text") + .help("Inline spec text (yaml/json/graphql SDL). One of specText | url required."), + ) + .arg( + Arg::new("url") + .long("url") + .help("URL to fetch the spec from (server-side)."), + ) + .arg( + Arg::new("version") + .long("version") + .help("Explicit version label; defaults to the spec's info.version."), + ) + .arg( + Arg::new("dist-tag") + .long("dist-tag") + .help("Dist-tag to publish this version under (default `latest`). `latest` makes\nit the current/default version; any other tag (`canary`, `beta`, …) is a\nside-channel — added without moving `latest`/current off the stable one."), + ) + .arg( + Arg::new("project-id") + .long("project-id") + .help("The project the API belongs to. Set server-side by the platform gateway\nfrom the caller's current project; defaults to the seeded project."), + ), + ) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .arg( + Arg::new("api-id") + .required(true), + ), + ) + .subcommand( + Command::new("update") + .about("Proxy: rename an entry (display name + description). id/ns immutable.") + .arg( + Arg::new("api-id") + .required(true), + ) + .arg( + Arg::new("name") + .long("name"), + ) + .arg( + Arg::new("description") + .long("description"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["apis", "dist-tags", "create"] => handle_apis_dist_tags_create(ctx, o, cmd_path, m).await, + ["apis", "list"] => handle_apis_list(ctx, o, cmd_path, m).await, + ["apis", "create"] => handle_apis_create(ctx, o, cmd_path, m).await, + ["apis", "retrieve"] => handle_apis_retrieve(ctx, o, cmd_path, m).await, + ["apis", "update"] => handle_apis_update(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_apis_dist_tags_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let api_id = m.get_one::("api-id").map(String::as_str).unwrap_or(""); + let path = format!("/apis/{}/dist-tags", runtime::path_escape(api_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("tag") { + body.insert("tag".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("version") { + body.insert("version".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_apis_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/apis".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_apis_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/apis".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("id") { + body.insert("id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("format") { + body.insert("format".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("kind") { + let value = serde_json::from_str::(v) + .unwrap_or_else(|_| serde_json::Value::String(v.clone())); + body.insert("kind".to_string(), value); + } + if let Some(v) = m.get_one::("ns") { + body.insert("ns".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("source") { + body.insert("source".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("spec-text") { + body.insert("specText".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("url") { + body.insert("url".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("version") { + body.insert("version".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("dist-tag") { + body.insert("distTag".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("project-id") { + body.insert("projectId".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_apis_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let api_id = m.get_one::("api-id").map(String::as_str).unwrap_or(""); + let path = format!("/apis/{}", runtime::path_escape(api_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_apis_update( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let api_id = m.get_one::("api-id").map(String::as_str).unwrap_or(""); + let path = format!("/apis/{}", runtime::path_escape(api_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("description") { + body.insert("description".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "PATCH", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/auth.rs b/packages/apitoolchain-api-cli/src/gen/cmd/auth.rs new file mode 100644 index 000000000..9ba393380 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/auth.rs @@ -0,0 +1,172 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("auth") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("login") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .arg( + Arg::new("email") + .long("email") + .required(true), + ) + .arg( + Arg::new("password") + .long("password") + .required(true), + ), + ), + ) + .subcommand( + Command::new("logout") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("create")), + ) + .subcommand( + Command::new("me") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("list")), + ) + .subcommand( + Command::new("register") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .arg( + Arg::new("email") + .long("email") + .required(true), + ) + .arg( + Arg::new("password") + .long("password") + .required(true), + ) + .arg( + Arg::new("name") + .long("name"), + ), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["auth", "login", "create"] => handle_auth_login_create(ctx, o, cmd_path, m).await, + ["auth", "logout", "create"] => handle_auth_logout_create(ctx, o, cmd_path, m).await, + ["auth", "me", "list"] => handle_auth_me_list(ctx, o, cmd_path, m).await, + ["auth", "register", "create"] => handle_auth_register_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_auth_login_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/auth/login".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("email") { + body.insert("email".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("password") { + body.insert("password".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_auth_logout_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/auth/logout".to_string(); + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_auth_me_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/auth/me".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_auth_register_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/auth/register".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("email") { + body.insert("email".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("password") { + body.insert("password".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/context.rs b/packages/apitoolchain-api-cli/src/gen/cmd/context.rs new file mode 100644 index 000000000..becdd79b6 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/context.rs @@ -0,0 +1,122 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("context") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("select") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Switch the caller's current project (persisted per-user).") + .arg( + Arg::new("project-id") + .long("project-id") + .required(true), + ), + ), + ) + .subcommand(Command::new("list")) + .subcommand( + Command::new("update") + .arg( + Arg::new("org-name") + .long("org-name"), + ) + .arg( + Arg::new("project-name") + .long("project-name"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["context", "select", "create"] => handle_context_select_create(ctx, o, cmd_path, m).await, + ["context", "list"] => handle_context_list(ctx, o, cmd_path, m).await, + ["context", "update"] => handle_context_update(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_context_select_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/context/select".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("project-id") { + body.insert("projectId".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_context_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/context".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_context_update( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/context".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("org-name") { + body.insert("orgName".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("project-name") { + body.insert("projectName".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "PATCH", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/docs_projects.rs b/packages/apitoolchain-api-cli/src/gen/cmd/docs_projects.rs new file mode 100644 index 000000000..c1d175cc2 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/docs_projects.rs @@ -0,0 +1,101 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("docs-projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .arg( + Arg::new("api-id") + .long("api-id"), + ), + ) + .subcommand( + Command::new("create") + .arg( + Arg::new("api-id") + .long("api-id") + .required(true), + ) + .arg( + Arg::new("name") + .long("name") + .required(true), + ) + .arg( + Arg::new("theme") + .long("theme"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["docs-projects", "list"] => handle_docs_projects_list(ctx, o, cmd_path, m).await, + ["docs-projects", "create"] => handle_docs_projects_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_docs_projects_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/docs-projects".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("api-id") { + query.push(("apiId", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_docs_projects_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/docs-projects".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("api-id") { + body.insert("apiId".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("theme") { + body.insert("theme".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/git_providers.rs b/packages/apitoolchain-api-cli/src/gen/cmd/git_providers.rs new file mode 100644 index 000000000..2c0a00fd0 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/git_providers.rs @@ -0,0 +1,199 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("git-providers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("branches") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Branch names of one repo (for the branch picker after a repo is chosen).") + .arg( + Arg::new("id") + .required(true), + ) + .arg( + Arg::new("repo") + .long("repo") + .required(true), + ), + ), + ) + .subcommand( + Command::new("repos") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("Repos the provider's token can access (for the connect picker).") + .arg( + Arg::new("id") + .required(true), + ), + ), + ) + .subcommand(Command::new("list")) + .subcommand( + Command::new("create") + .arg( + Arg::new("kind") + .long("kind") + .help("The git platforms the gitprovider service can connect to.") + .required(true), + ) + .arg( + Arg::new("name") + .long("name"), + ) + .arg( + Arg::new("base-url") + .long("base-url") + .help("Provider base URL; optional for github.com/gitlab.com, required for self-hosted."), + ) + .arg( + Arg::new("token") + .long("token") + .required(true), + ), + ) + .subcommand( + Command::new("delete") + .arg( + Arg::new("id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["git-providers", "branches", "list"] => handle_git_providers_branches_list(ctx, o, cmd_path, m).await, + ["git-providers", "repos", "list"] => handle_git_providers_repos_list(ctx, o, cmd_path, m).await, + ["git-providers", "list"] => handle_git_providers_list(ctx, o, cmd_path, m).await, + ["git-providers", "create"] => handle_git_providers_create(ctx, o, cmd_path, m).await, + ["git-providers", "delete"] => handle_git_providers_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_git_providers_branches_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/git-providers/{}/branches", runtime::path_escape(id)); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("repo") { + query.push(("repo", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_git_providers_repos_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/git-providers/{}/repos", runtime::path_escape(id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_git_providers_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/git-providers".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_git_providers_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/git-providers".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("kind") { + body.insert("kind".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("base-url") { + body.insert("baseUrl".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("token") { + body.insert("token".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_git_providers_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/git-providers/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/mcp_servers.rs b/packages/apitoolchain-api-cli/src/gen/cmd/mcp_servers.rs new file mode 100644 index 000000000..b9826fba3 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/mcp_servers.rs @@ -0,0 +1,108 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("mcp-servers") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .arg( + Arg::new("api-id") + .long("api-id"), + ), + ) + .subcommand( + Command::new("create") + .arg( + Arg::new("api-id") + .long("api-id") + .required(true), + ) + .arg( + Arg::new("name") + .long("name") + .required(true), + ) + .arg( + Arg::new("transport") + .long("transport"), + ) + .arg( + Arg::new("url") + .long("url"), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["mcp-servers", "list"] => handle_mcp_servers_list(ctx, o, cmd_path, m).await, + ["mcp-servers", "create"] => handle_mcp_servers_create(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_mcp_servers_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/mcp-servers".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("api-id") { + query.push(("apiId", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_mcp_servers_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/mcp-servers".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("api-id") { + body.insert("apiId".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("transport") { + body.insert("transport".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("url") { + body.insert("url".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/members.rs b/packages/apitoolchain-api-cli/src/gen/cmd/members.rs new file mode 100644 index 000000000..bc403b8a1 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/members.rs @@ -0,0 +1,153 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("members") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("role") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("update") + .arg( + Arg::new("user-id") + .required(true), + ) + .arg( + Arg::new("role") + .long("role") + .required(true), + ), + ), + ) + .subcommand(Command::new("list")) + .subcommand( + Command::new("create") + .arg( + Arg::new("email") + .long("email") + .required(true), + ) + .arg( + Arg::new("role") + .long("role"), + ), + ) + .subcommand( + Command::new("delete") + .arg( + Arg::new("user-id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["members", "role", "update"] => handle_members_role_update(ctx, o, cmd_path, m).await, + ["members", "list"] => handle_members_list(ctx, o, cmd_path, m).await, + ["members", "create"] => handle_members_create(ctx, o, cmd_path, m).await, + ["members", "delete"] => handle_members_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_members_role_update( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let path = format!("/members/{}/role", runtime::path_escape(user_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("role") { + body.insert("role".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "PATCH", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_members_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/members".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_members_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/members".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("email") { + body.insert("email".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("role") { + body.insert("role".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_members_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let user_id = m.get_one::("user-id").map(String::as_str).unwrap_or(""); + let path = format!("/members/{}", runtime::path_escape(user_id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/mod.rs b/packages/apitoolchain-api-cli/src/gen/cmd/mod.rs new file mode 100644 index 000000000..de917e907 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/mod.rs @@ -0,0 +1,20 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod api_keys; +pub mod apis; +pub mod auth; +pub mod context; +pub mod docs_projects; +pub mod git_providers; +pub mod mcp_servers; +pub mod members; +pub mod notifications; +pub mod overview; +pub mod package_registries; +pub mod projects; +pub mod registry_connections; +pub mod releases; +pub mod repo_connections; +pub mod sdk_targets; +pub mod sdks; +pub mod usage; diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/notifications.rs b/packages/apitoolchain-api-cli/src/gen/cmd/notifications.rs new file mode 100644 index 000000000..c819e2172 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/notifications.rs @@ -0,0 +1,93 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("notifications") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("read") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .arg( + Arg::new("ids") + .long("ids") + .action(clap::ArgAction::Append) + .value_delimiter(','), + ) + .arg( + Arg::new("all") + .long("all") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)), + ), + ), + ) + .subcommand(Command::new("list")) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["notifications", "read", "create"] => handle_notifications_read_create(ctx, o, cmd_path, m).await, + ["notifications", "list"] => handle_notifications_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_notifications_read_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/notifications/read".to_string(); + let mut body = serde_json::Map::new(); + if let Some(vs) = m.get_many::("ids") { + body.insert("ids".to_string(), serde_json::Value::Array(vs.cloned().map(serde_json::Value::String).collect())); + } + if let Some(v) = m.get_one::("all") { + body.insert("all".to_string(), serde_json::Value::Bool(*v)); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_notifications_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/notifications".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/overview.rs b/packages/apitoolchain-api-cli/src/gen/cmd/overview.rs new file mode 100644 index 000000000..18c68c551 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/overview.rs @@ -0,0 +1,47 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("overview") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("stats") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("list")), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["overview", "stats", "list"] => handle_overview_stats_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_overview_stats_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/overview/stats".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/package_registries.rs b/packages/apitoolchain-api-cli/src/gen/cmd/package_registries.rs new file mode 100644 index 000000000..865f4f661 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/package_registries.rs @@ -0,0 +1,127 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("package-registries") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("list")) + .subcommand( + Command::new("create") + .arg( + Arg::new("kind") + .long("kind") + .help("Package registry flavour — the language ecosystem an SDK publishes into.") + .required(true), + ) + .arg( + Arg::new("name") + .long("name"), + ) + .arg( + Arg::new("url") + .long("url") + .help("Registry URL, or a local file-feed path.") + .required(true), + ) + .arg( + Arg::new("token") + .long("token") + .help("Auth token; may be empty for an anonymous/local registry."), + ), + ) + .subcommand( + Command::new("delete") + .arg( + Arg::new("id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["package-registries", "list"] => handle_package_registries_list(ctx, o, cmd_path, m).await, + ["package-registries", "create"] => handle_package_registries_create(ctx, o, cmd_path, m).await, + ["package-registries", "delete"] => handle_package_registries_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_package_registries_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/package-registries".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_package_registries_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/package-registries".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("kind") { + body.insert("kind".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("url") { + body.insert("url".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("token") { + body.insert("token".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_package_registries_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/package-registries/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/projects.rs b/packages/apitoolchain-api-cli/src/gen/cmd/projects.rs new file mode 100644 index 000000000..7b47b03aa --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/projects.rs @@ -0,0 +1,141 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("projects") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("list")) + .subcommand( + Command::new("create") + .arg( + Arg::new("name") + .long("name") + .required(true), + ), + ) + .subcommand( + Command::new("update") + .arg( + Arg::new("id") + .required(true), + ) + .arg( + Arg::new("name") + .long("name") + .required(true), + ), + ) + .subcommand( + Command::new("delete") + .arg( + Arg::new("id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["projects", "list"] => handle_projects_list(ctx, o, cmd_path, m).await, + ["projects", "create"] => handle_projects_create(ctx, o, cmd_path, m).await, + ["projects", "update"] => handle_projects_update(ctx, o, cmd_path, m).await, + ["projects", "delete"] => handle_projects_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_projects_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + _m: &ArgMatches, +) -> ExitCode { + let path = "/projects".to_string(); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_projects_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/projects".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_projects_update( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/projects/{}", runtime::path_escape(id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "PATCH", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_projects_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/projects/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/registry_connections.rs b/packages/apitoolchain-api-cli/src/gen/cmd/registry_connections.rs new file mode 100644 index 000000000..87a486e1f --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/registry_connections.rs @@ -0,0 +1,165 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("registry-connections") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .arg( + Arg::new("target-id") + .long("target-id"), + ), + ) + .subcommand( + Command::new("create") + .arg( + Arg::new("registry-id") + .long("registry-id") + .required(true), + ) + .arg( + Arg::new("target-id") + .long("target-id") + .required(true), + ) + .arg( + Arg::new("package-name") + .long("package-name") + .help("Override the published package name (defaults to the SDK's own name)."), + ) + .arg( + Arg::new("auto-publish") + .long("auto-publish") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)), + ), + ) + .subcommand( + Command::new("delete") + .arg( + Arg::new("id") + .required(true), + ), + ) + .subcommand( + Command::new("publish") + .arg( + Arg::new("id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["registry-connections", "list"] => handle_registry_connections_list(ctx, o, cmd_path, m).await, + ["registry-connections", "create"] => handle_registry_connections_create(ctx, o, cmd_path, m).await, + ["registry-connections", "delete"] => handle_registry_connections_delete(ctx, o, cmd_path, m).await, + ["registry-connections", "publish"] => handle_registry_connections_publish(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_registry_connections_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/registry-connections".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("target-id") { + query.push(("targetId", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_registry_connections_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/registry-connections".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("registry-id") { + body.insert("registryId".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("target-id") { + body.insert("targetId".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("package-name") { + body.insert("packageName".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("auto-publish") { + body.insert("autoPublish".to_string(), serde_json::Value::Bool(*v)); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_registry_connections_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/registry-connections/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_registry_connections_publish( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/registry-connections/{}/publish", runtime::path_escape(id)); + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/releases.rs b/packages/apitoolchain-api-cli/src/gen/cmd/releases.rs new file mode 100644 index 000000000..5996de5ba --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/releases.rs @@ -0,0 +1,151 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("releases") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("prepare") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .arg( + Arg::new("connection-id") + .long("connection-id") + .required(true), + ) + .arg( + Arg::new("version-override") + .long("version-override"), + ), + ), + ) + .subcommand( + Command::new("list") + .arg( + Arg::new("connection-id") + .long("connection-id"), + ), + ) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .arg( + Arg::new("id") + .required(true), + ), + ) + .subcommand( + Command::new("publish") + .arg( + Arg::new("id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["releases", "prepare", "create"] => handle_releases_prepare_create(ctx, o, cmd_path, m).await, + ["releases", "list"] => handle_releases_list(ctx, o, cmd_path, m).await, + ["releases", "retrieve"] => handle_releases_retrieve(ctx, o, cmd_path, m).await, + ["releases", "publish"] => handle_releases_publish(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_releases_prepare_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/releases/prepare".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("connection-id") { + body.insert("connectionId".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("version-override") { + body.insert("versionOverride".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_releases_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/releases".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("connection-id") { + query.push(("connectionId", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_releases_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/releases/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_releases_publish( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/releases/{}/publish", runtime::path_escape(id)); + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/repo_connections.rs b/packages/apitoolchain-api-cli/src/gen/cmd/repo_connections.rs new file mode 100644 index 000000000..3c706e775 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/repo_connections.rs @@ -0,0 +1,337 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("repo-connections") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("release-config") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .about("Switch a connection between direct-push and PR-based release mode.") + .arg( + Arg::new("id") + .required(true), + ) + .arg( + Arg::new("release-mode") + .long("release-mode") + .required(true), + ) + .arg( + Arg::new("auto-release") + .long("auto-release") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .required(true), + ) + .arg( + Arg::new("base-branch") + .long("base-branch"), + ) + .arg( + Arg::new("prerelease") + .long("prerelease") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)), + ) + .arg( + Arg::new("dist-tags") + .long("dist-tags") + .help("Comma-separated dist-tags this connection reacts to (default `latest`);\n`*` = all. Only a version published under a matching tag triggers a\nrelease."), + ), + ), + ) + .subcommand( + Command::new("sync") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .arg( + Arg::new("id") + .required(true), + ), + ), + ) + .subcommand( + Command::new("list") + .arg( + Arg::new("target-kind") + .long("target-kind"), + ) + .arg( + Arg::new("target-id") + .long("target-id"), + ), + ) + .subcommand( + Command::new("create") + .arg( + Arg::new("provider-id") + .long("provider-id") + .required(true), + ) + .arg( + Arg::new("target-kind") + .long("target-kind") + .help("What a repo connection syncs into a repo.") + .required(true), + ) + .arg( + Arg::new("target-id") + .long("target-id") + .required(true), + ) + .arg( + Arg::new("ref") + .long("ref"), + ) + .arg( + Arg::new("repo") + .long("repo") + .help("For an existing repo: \"owner/name\". For a new repo: just the name.") + .required(true), + ) + .arg( + Arg::new("create-repo") + .long("create-repo") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("When true, create `repo` (a name) under the account before connecting."), + ) + .arg( + Arg::new("make-private") + .long("make-private") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)) + .help("Visibility for a newly-created repo."), + ) + .arg( + Arg::new("branch") + .long("branch"), + ) + .arg( + Arg::new("prefix") + .long("prefix"), + ) + .arg( + Arg::new("release-mode") + .long("release-mode"), + ) + .arg( + Arg::new("auto-release") + .long("auto-release") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("true") + .value_parser(clap::value_parser!(bool)), + ) + .arg( + Arg::new("base-branch") + .long("base-branch"), + ) + .arg( + Arg::new("dist-tags") + .long("dist-tags") + .help("Comma-separated dist-tags to react to (default `latest`; `*` = all)."), + ), + ) + .subcommand( + Command::new("delete") + .arg( + Arg::new("id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["repo-connections", "release-config", "create"] => handle_repo_connections_release_config_create(ctx, o, cmd_path, m).await, + ["repo-connections", "sync", "create"] => handle_repo_connections_sync_create(ctx, o, cmd_path, m).await, + ["repo-connections", "list"] => handle_repo_connections_list(ctx, o, cmd_path, m).await, + ["repo-connections", "create"] => handle_repo_connections_create(ctx, o, cmd_path, m).await, + ["repo-connections", "delete"] => handle_repo_connections_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_repo_connections_release_config_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/repo-connections/{}/release-config", runtime::path_escape(id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("release-mode") { + body.insert("releaseMode".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("auto-release") { + body.insert("autoRelease".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("base-branch") { + body.insert("baseBranch".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("prerelease") { + body.insert("prerelease".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("dist-tags") { + body.insert("distTags".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_repo_connections_sync_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/repo-connections/{}/sync", runtime::path_escape(id)); + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_repo_connections_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/repo-connections".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("target-kind") { + query.push(("targetKind", v.clone())); + } + if let Some(v) = m.get_one::("target-id") { + query.push(("targetId", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_repo_connections_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/repo-connections".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("provider-id") { + body.insert("providerId".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("target-kind") { + body.insert("targetKind".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("target-id") { + body.insert("targetId".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("ref") { + body.insert("ref".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("repo") { + body.insert("repo".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("create-repo") { + body.insert("createRepo".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("make-private") { + body.insert("makePrivate".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("branch") { + body.insert("branch".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("prefix") { + body.insert("prefix".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("release-mode") { + body.insert("releaseMode".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("auto-release") { + body.insert("autoRelease".to_string(), serde_json::Value::Bool(*v)); + } + if let Some(v) = m.get_one::("base-branch") { + body.insert("baseBranch".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("dist-tags") { + body.insert("distTags".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_repo_connections_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/repo-connections/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/sdk_targets.rs b/packages/apitoolchain-api-cli/src/gen/cmd/sdk_targets.rs new file mode 100644 index 000000000..2b2dd8a91 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/sdk_targets.rs @@ -0,0 +1,105 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("sdk-targets") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .arg( + Arg::new("api-id") + .long("api-id"), + ), + ) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .arg( + Arg::new("id") + .required(true), + ), + ) + .subcommand( + Command::new("delete") + .arg( + Arg::new("id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["sdk-targets", "list"] => handle_sdk_targets_list(ctx, o, cmd_path, m).await, + ["sdk-targets", "retrieve"] => handle_sdk_targets_retrieve(ctx, o, cmd_path, m).await, + ["sdk-targets", "delete"] => handle_sdk_targets_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_sdk_targets_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/sdk-targets".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("api-id") { + query.push(("apiId", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdk_targets_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/sdk-targets/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdk_targets_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/sdk-targets/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/sdks.rs b/packages/apitoolchain-api-cli/src/gen/cmd/sdks.rs new file mode 100644 index 000000000..86bd598a1 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/sdks.rs @@ -0,0 +1,386 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("sdks") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("build") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("create") + .arg( + Arg::new("sdk-id") + .required(true), + ) + .arg( + Arg::new("api-version") + .long("api-version") + .help("API version to build every target from; defaults to the API's current."), + ), + ), + ) + .subcommand( + Command::new("targets") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .arg( + Arg::new("sdk-id") + .required(true), + ), + ) + .subcommand( + Command::new("create") + .arg( + Arg::new("sdk-id") + .required(true), + ) + .arg( + Arg::new("language") + .long("language") + .required(true), + ) + .arg( + Arg::new("package-name") + .long("package-name"), + ) + .arg( + Arg::new("version") + .long("version") + .help("API version to generate from; defaults to the API's current version."), + ) + .arg( + Arg::new("sdk-json") + .long("sdk-json") + .help("A full custom sdk.json (behavior + per-language emitter options), serialized.\nWhen set it's applied over the derived defaults during generation (the\n\"wizard\" flow); empty → the auto-derived config."), + ), + ), + ) + .subcommand( + Command::new("versions") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .about("SDK version/build history (newest first).") + .arg( + Arg::new("sdk-id") + .required(true), + ), + ) + .subcommand( + Command::new("create") + .about("Create a new SDK version: bump + rebuild every target, recorded as a build.") + .arg( + Arg::new("sdk-id") + .required(true), + ) + .arg( + Arg::new("version") + .long("version") + .help("The new SDK version (e.g. \"0.2.0\").") + .required(true), + ) + .arg( + Arg::new("api-version") + .long("api-version") + .help("API version to build from; defaults to the API's current."), + ), + ), + ) + .subcommand( + Command::new("list") + .arg( + Arg::new("api-id") + .long("api-id"), + ), + ) + .subcommand( + Command::new("create") + .arg( + Arg::new("api-id") + .long("api-id") + .required(true), + ) + .arg( + Arg::new("name") + .long("name"), + ) + .arg( + Arg::new("id") + .long("id") + .help("Explicit id/slug for the SDK. Slugified server-side; defaults to a slug of\n`name`. Lets the display title (`name`) be decoupled from the stable id."), + ) + .arg( + Arg::new("description") + .long("description"), + ) + .arg( + Arg::new("ns") + .long("ns"), + ), + ) + .subcommand( + Command::new("retrieve") + .visible_alias("get") + .arg( + Arg::new("id") + .required(true), + ), + ) + .subcommand( + Command::new("delete") + .arg( + Arg::new("id") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["sdks", "build", "create"] => handle_sdks_build_create(ctx, o, cmd_path, m).await, + ["sdks", "targets", "list"] => handle_sdks_targets_list(ctx, o, cmd_path, m).await, + ["sdks", "targets", "create"] => handle_sdks_targets_create(ctx, o, cmd_path, m).await, + ["sdks", "versions", "list"] => handle_sdks_versions_list(ctx, o, cmd_path, m).await, + ["sdks", "versions", "create"] => handle_sdks_versions_create(ctx, o, cmd_path, m).await, + ["sdks", "list"] => handle_sdks_list(ctx, o, cmd_path, m).await, + ["sdks", "create"] => handle_sdks_create(ctx, o, cmd_path, m).await, + ["sdks", "retrieve"] => handle_sdks_retrieve(ctx, o, cmd_path, m).await, + ["sdks", "delete"] => handle_sdks_delete(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_sdks_build_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let sdk_id = m.get_one::("sdk-id").map(String::as_str).unwrap_or(""); + let path = format!("/sdks/{}/build", runtime::path_escape(sdk_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("api-version") { + body.insert("apiVersion".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdks_targets_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let sdk_id = m.get_one::("sdk-id").map(String::as_str).unwrap_or(""); + let path = format!("/sdks/{}/targets", runtime::path_escape(sdk_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdks_targets_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let sdk_id = m.get_one::("sdk-id").map(String::as_str).unwrap_or(""); + let path = format!("/sdks/{}/targets", runtime::path_escape(sdk_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("language") { + body.insert("language".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("package-name") { + body.insert("packageName".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("version") { + body.insert("version".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("sdk-json") { + body.insert("sdkJson".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdks_versions_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let sdk_id = m.get_one::("sdk-id").map(String::as_str).unwrap_or(""); + let path = format!("/sdks/{}/versions", runtime::path_escape(sdk_id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdks_versions_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let sdk_id = m.get_one::("sdk-id").map(String::as_str).unwrap_or(""); + let path = format!("/sdks/{}/versions", runtime::path_escape(sdk_id)); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("version") { + body.insert("version".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("api-version") { + body.insert("apiVersion".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdks_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/sdks".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("api-id") { + query.push(("apiId", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdks_create( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/sdks".to_string(); + let mut body = serde_json::Map::new(); + if let Some(v) = m.get_one::("api-id") { + body.insert("apiId".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("name") { + body.insert("name".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("id") { + body.insert("id".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("description") { + body.insert("description".to_string(), serde_json::Value::String(v.clone())); + } + if let Some(v) = m.get_one::("ns") { + body.insert("ns".to_string(), serde_json::Value::String(v.clone())); + } + let body_bytes = match serde_json::to_vec(&serde_json::Value::Object(body)) { + Ok(bytes) => bytes, + Err(err) => return o.print_error(cmd_path, &runtime::Error::Invalid(err.to_string())), + }; + let req = runtime::Request { + method: "POST", + path, + query: Vec::new(), + headers: Vec::new(), + body: Some(body_bytes), + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdks_retrieve( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/sdks/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "GET", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} + +async fn handle_sdks_delete( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let id = m.get_one::("id").map(String::as_str).unwrap_or(""); + let path = format!("/sdks/{}", runtime::path_escape(id)); + let req = runtime::Request { + method: "DELETE", + path, + query: Vec::new(), + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/cmd/usage.rs b/packages/apitoolchain-api-cli/src/gen/cmd/usage.rs new file mode 100644 index 000000000..2100a56d1 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/cmd/usage.rs @@ -0,0 +1,53 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use clap::{Arg, ArgMatches, Command}; + +use crate::gen::runtime::{self, CliOverrides, Context}; + +pub fn command() -> Command { + Command::new("usage") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("list") + .arg( + Arg::new("range") + .long("range") + .required(true), + ), + ) +} + +pub async fn run(ctx: &Context, o: &O, cmd_path: &[String], m: &ArgMatches) -> ExitCode { + let parts: Vec<&str> = cmd_path.iter().map(String::as_str).collect(); + match parts.as_slice() { + ["usage", "list"] => handle_usage_list(ctx, o, cmd_path, m).await, + _ => { + eprintln!("error: unknown command: {}", cmd_path.join(" ")); + ExitCode::FAILURE + } + } +} + +async fn handle_usage_list( + ctx: &Context, + o: &O, + cmd_path: &[String], + m: &ArgMatches, +) -> ExitCode { + let path = "/usage".to_string(); + let mut query: Vec<(&'static str, String)> = Vec::new(); + if let Some(v) = m.get_one::("range") { + query.push(("range", v.clone())); + } + let req = runtime::Request { + method: "GET", + path, + query, + headers: Vec::new(), + body: None, + }; + runtime::run_request(ctx, o, cmd_path, req).await +} diff --git a/packages/apitoolchain-api-cli/src/gen/mod.rs b/packages/apitoolchain-api-cli/src/gen/mod.rs new file mode 100644 index 000000000..3906dd730 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/mod.rs @@ -0,0 +1,5 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +pub mod cli; +pub mod cmd; +pub mod runtime; diff --git a/packages/apitoolchain-api-cli/src/gen/runtime/config.rs b/packages/apitoolchain-api-cli/src/gen/runtime/config.rs new file mode 100644 index 000000000..abb0f25be --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/runtime/config.rs @@ -0,0 +1,18 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Spec-derived configuration: the API base URL and environment-based auth. + +const DEFAULT_BASE_URL: &str = ""; + +/// The API base URL, overridable via `APITOOLCHAIN_BASE_URL`. +pub fn base_url() -> String { + match std::env::var("APITOOLCHAIN_BASE_URL") { + Ok(v) if !v.is_empty() => v, + _ => DEFAULT_BASE_URL.to_string(), + } +} + +/// Attach credentials read from the environment to the request. +pub fn apply_auth(rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder { + rb +} diff --git a/packages/apitoolchain-api-cli/src/gen/runtime/custom.rs b/packages/apitoolchain-api-cli/src/gen/runtime/custom.rs new file mode 100644 index 000000000..693254f72 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/runtime/custom.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::future::Future; +use std::pin::Pin; + +use clap::{ArgMatches, Command}; + +use super::http::Error; +use super::Context; + +/// The boxed async handler for one custom command (owned inputs, so plain +/// `|ctx, m| async move { ... }` closures fit without lifetime plumbing). +pub type CustomHandler = Box Pin>>>>; + +struct CustomEntry { + under: Vec, + command: Command, + handler: CustomHandler, +} + +/// Hand-written commands registered from `src/custom/mod.rs`, grafted into the +/// generated tree before parsing and dispatched BEFORE generated handlers — a +/// custom command registered on an existing path overrides its behavior. +#[derive(Default)] +pub struct CustomCommands { + entries: Vec, +} + +impl CustomCommands { + pub fn new() -> Self { + Self::default() + } + + /// Add `command` under a parent path (`&[]` = top level): + /// `add(&["tools"], Command::new("hello"), |ctx, m| async move { ... })` + /// registers ` tools hello`. Missing parents are created. When the full + /// path already exists in the generated tree, the generated clap definition + /// keeps handling parsing/help, but YOUR handler runs instead. + #[allow(dead_code)] // user-facing API, called from src/custom/mod.rs + pub fn add(&mut self, under: &[&str], command: Command, handler: H) + where + H: Fn(Context, ArgMatches) -> F + 'static, + F: Future> + 'static, + { + self.entries.push(CustomEntry { + under: under.iter().map(|s| s.to_string()).collect(), + command, + handler: Box::new(move |ctx, m| Box::pin(handler(ctx, m))), + }); + } + + /// Graft registered commands into the generated clap tree (new paths only — + /// existing definitions are left untouched; dispatch still prefers custom). + pub(crate) fn graft(&self, mut root: Command) -> Command { + for entry in &self.entries { + let mut full = entry.under.clone(); + full.push(entry.command.get_name().to_string()); + if path_exists(&root, &full) { + continue; + } + root = graft_at(root, &entry.under, entry.command.clone()); + } + root + } + + /// The handler registered for exactly this invoked command path, if any. + pub(crate) fn find(&self, path: &[String]) -> Option<&CustomHandler> { + self.entries + .iter() + .find(|e| { + e.under.len() + 1 == path.len() + && e.under.iter().zip(path.iter()).all(|(a, b)| a == b) + && path.last().map(String::as_str) == Some(e.command.get_name()) + }) + .map(|e| &e.handler) + } +} + +fn path_exists(root: &Command, path: &[String]) -> bool { + let mut current = root; + for name in path { + match current.find_subcommand(name) { + Some(sub) => current = sub, + None => return false, + } + } + true +} + +fn graft_at(parent: Command, under: &[String], cmd: Command) -> Command { + match under.split_first() { + None => parent.subcommand(cmd), + Some((head, rest)) => { + let rest = rest.to_vec(); + if parent.find_subcommand(head).is_some() { + parent.mut_subcommand(head.clone(), move |sub| graft_at(sub, &rest, cmd)) + } else { + let node = Command::new(head.clone()).subcommand_required(true).arg_required_else_help(true); + parent.subcommand(graft_at(node, &rest, cmd)) + } + } + } +} diff --git a/packages/apitoolchain-api-cli/src/gen/runtime/http.rs b/packages/apitoolchain-api-cli/src/gen/runtime/http.rs new file mode 100644 index 000000000..9ee976257 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/runtime/http.rs @@ -0,0 +1,50 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! Static HTTP primitives shared by every command handler. + +use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; +use serde_json::Value; + +/// Escape a value for use inside a URL path segment (strict RFC 3986 unreserved). +const PATH_SEGMENT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'_').remove(b'.').remove(b'~'); + +pub fn path_escape(value: &str) -> String { + utf8_percent_encode(value, PATH_SEGMENT).to_string() +} + +/// A generic API request assembled by a command handler. +pub struct Request { + pub method: &'static str, + pub path: String, + pub query: Vec<(&'static str, String)>, + pub headers: Vec<(&'static str, String)>, + pub body: Option>, +} + +/// A request failure, inspectable from `CliOverrides::recover_error` / `print_error`. +#[derive(Debug)] +pub enum Error { + /// The server answered with a 4xx/5xx status; `body` is the decoded response. + Http { status: u16, body: Value }, + /// The request could not be sent or the response could not be read. + Transport(String), + /// A handler-side failure (flag decoding, body assembly, custom command logic). + Invalid(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Http { status, body } => write!(f, "request failed: {status}: {body}"), + Error::Transport(message) => write!(f, "request error: {message}"), + Error::Invalid(message) => write!(f, "{message}"), + } + } +} + +impl std::error::Error for Error {} + +/// Decode a response body: JSON when it parses, the raw text otherwise. +pub(crate) fn decode(bytes: &[u8]) -> Value { + serde_json::from_slice(bytes).unwrap_or_else(|_| Value::String(String::from_utf8_lossy(bytes).into_owned())) +} diff --git a/packages/apitoolchain-api-cli/src/gen/runtime/mod.rs b/packages/apitoolchain-api-cli/src/gen/runtime/mod.rs new file mode 100644 index 000000000..f03c99a28 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/runtime/mod.rs @@ -0,0 +1,103 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +//! The vendored runtime: request execution context, override hooks, and the +//! custom-command registry. Extension points live in `overrides.rs` (the +//! `CliOverrides` trait) and `custom.rs` (the `CustomCommands` registry). + +pub mod config; +mod custom; +mod http; +mod overrides; + +pub use custom::CustomCommands; +pub use http::{path_escape, Error, Request}; +pub use overrides::CliOverrides; + +use std::process::ExitCode; + +use serde_json::Value; + +/// Everything a handler (generated or custom) needs to reach the API: the +/// configured base URL and an HTTP client with the spec's auth applied. +#[derive(Clone)] +pub struct Context { + pub base_url: String, + http: reqwest::Client, +} + +impl Context { + pub fn from_env() -> Self { + Self { + base_url: config::base_url(), + http: reqwest::Client::new(), + } + } + + /// Execute against the configured base URL with the spec's auth applied, + /// decoding the response (JSON when it parses, raw text otherwise). + pub async fn execute(&self, req: Request) -> Result { + let (status, bytes) = self.send(req).await?; + let value = http::decode(&bytes); + if status >= 400 { + return Err(Error::Http { status, body: value }); + } + Ok(value) + } + + /// Raw-bytes variant of [`execute`](Self::execute) for binary/non-JSON endpoints. + #[allow(dead_code)] // user-facing API for custom command handlers + pub async fn execute_raw(&self, req: Request) -> Result, Error> { + let (status, bytes) = self.send(req).await?; + if status >= 400 { + return Err(Error::Http { status, body: http::decode(&bytes) }); + } + Ok(bytes) + } + + async fn send(&self, req: Request) -> Result<(u16, Vec), Error> { + let endpoint = format!("{}{}", self.base_url.trim_end_matches('/'), req.path); + let method = reqwest::Method::from_bytes(req.method.as_bytes()).map_err(|e| Error::Invalid(e.to_string()))?; + let mut rb = self.http.request(method, &endpoint); + if !req.query.is_empty() { + rb = rb.query(&req.query); + } + let has_content_type = req.headers.iter().any(|(k, _)| k.eq_ignore_ascii_case("content-type")); + for (key, value) in &req.headers { + rb = rb.header(*key, value.as_str()); + } + if let Some(body) = req.body { + if !has_content_type { + rb = rb.header("Content-Type", "application/json"); + } + rb = rb.body(body); + } + rb = config::apply_auth(rb); + let resp = rb.send().await.map_err(|e| Error::Transport(e.to_string()))?; + let status = resp.status().as_u16(); + let bytes = resp.bytes().await.map_err(|e| Error::Transport(e.to_string()))?.to_vec(); + Ok((status, bytes)) + } +} + +/// Thread one generated request through the override hooks: mutate, execute, +/// transform, recover, then print. Printing happens ONLY here (via the trait), +/// so overrides fully control the CLI's output. +pub async fn run_request( + ctx: &Context, + o: &O, + cmd_path: &[String], + mut req: Request, +) -> ExitCode { + o.before_request(ctx, cmd_path, &mut req); + let outcome = match ctx.execute(req).await { + Ok(value) => o.transform_response(ctx, cmd_path, value), + Err(err) => Err(err), + }; + match outcome.or_else(|err| o.recover_error(ctx, cmd_path, err)) { + Ok(value) => { + o.print_success(cmd_path, &value); + ExitCode::SUCCESS + } + Err(err) => o.print_error(cmd_path, &err), + } +} diff --git a/packages/apitoolchain-api-cli/src/gen/runtime/overrides.rs b/packages/apitoolchain-api-cli/src/gen/runtime/overrides.rs new file mode 100644 index 000000000..e1af75754 --- /dev/null +++ b/packages/apitoolchain-api-cli/src/gen/runtime/overrides.rs @@ -0,0 +1,51 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +use std::process::ExitCode; + +use serde_json::Value; + +use super::http::{Error, Request}; +use super::Context; + +/// The override seam for every generated command. Implement this on your type in +/// `src/custom/mod.rs` — every method has a default, so `impl CliOverrides for +/// Custom {}` compiles and behaves exactly like the stock CLI. +pub trait CliOverrides { + /// Mutate the assembled request before it is sent (extra headers, alternate + /// auth, tracing). `cmd_path` is the invoked command path, e.g. `["models", "list"]`. + fn before_request(&self, ctx: &Context, cmd_path: &[String], req: &mut Request) { + let _ = (ctx, cmd_path, req); + } + + /// Transform the decoded response before printing. + fn transform_response(&self, ctx: &Context, cmd_path: &[String], value: Value) -> Result { + let _ = (ctx, cmd_path); + Ok(value) + } + + /// Recover from a failure: return `Ok(value)` to turn it into a success, + /// or hand the error back to keep failing. + fn recover_error(&self, ctx: &Context, cmd_path: &[String], err: Error) -> Result { + let _ = (ctx, cmd_path); + Err(err) + } + + /// Print a successful result. Default: pretty JSON (raw text for non-JSON bodies). + fn print_success(&self, cmd_path: &[String], value: &Value) { + let _ = cmd_path; + match value { + Value::String(text) => println!("{text}"), + other => match serde_json::to_string_pretty(other) { + Ok(text) => println!("{text}"), + Err(_) => println!("{other}"), + }, + } + } + + /// Print a failure and choose the process exit code. + fn print_error(&self, cmd_path: &[String], err: &Error) -> ExitCode { + let _ = cmd_path; + eprintln!("error: {err}"); + ExitCode::FAILURE + } +} diff --git a/packages/apitoolchain-api-cli/src/main.rs b/packages/apitoolchain-api-cli/src/main.rs new file mode 100644 index 000000000..99acaa6ae --- /dev/null +++ b/packages/apitoolchain-api-cli/src/main.rs @@ -0,0 +1,11 @@ +// Code generated by opencli2rust. DO NOT EDIT. + +mod custom; +mod gen; + +#[tokio::main] +async fn main() -> std::process::ExitCode { + let mut commands = gen::runtime::CustomCommands::new(); + custom::register(&mut commands); + gen::cli::run(custom::overrides(), commands).await +} diff --git a/packages/apitoolchain-api-node/src/models.ts b/packages/apitoolchain-api-node/src/models.ts index 65f479048..d4fd44a7c 100644 --- a/packages/apitoolchain-api-node/src/models.ts +++ b/packages/apitoolchain-api-node/src/models.ts @@ -346,6 +346,11 @@ export interface ApitoolchainRegistryConnection { packageName: string; /** Publish automatically when a release for this target is merged. */ autoPublish: boolean; + /** + * The PUBLISHER's own log (extract → apply name → push → result) — reset each + * publish + appended live, so the dashboard can tail it. + */ + publishLogs: string; lastPublishedVersion?: string; lastPublishedAt?: string; lastPublishStatus?: ApitoolchainBuildStatus; @@ -483,8 +488,24 @@ export interface ApitoolchainSdkTarget { output: string; version: string; status: ApitoolchainBuildStatus; + /** + * The target's own generation log (`[] …` lines) — reset each build and + * appended live, so the dashboard can tail the current (re)generation. + */ + buildLogs: string; lastPublishedAt?: string; registryUrl?: string; + /** + * The custom sdk.json applied at generation (the "wizard" config), serialized; + * empty when the target used the auto-derived config. + */ + sdkJson?: string; + /** + * The saved sdk.json config has UNBUILT changes — set when the config is edited + * (the wizard "Save"), cleared when a build applies it. Drives the persistent + * "pending changes, waiting for a build" status in the UI. + */ + configPending: boolean; } export type ApitoolchainSdkLanguage = "go" | "python" | "node" | "ruby" | "java" | "dotnet"; @@ -528,6 +549,12 @@ export interface ApitoolchainAddSdkTargetInput { packageName?: string; /** API version to generate from; defaults to the API's current version. */ version?: string; + /** + * A full custom sdk.json (behavior + per-language emitter options), serialized. + * When set it's applied over the derived defaults during generation (the + * "wizard" flow); empty → the auto-derived config. + */ + sdkJson?: string; } /** diff --git a/packages/apitoolchain-api-node/src/resources/sdks.ts b/packages/apitoolchain-api-node/src/resources/sdks.ts index 8c6665a1c..cf7f3fa52 100644 --- a/packages/apitoolchain-api-node/src/resources/sdks.ts +++ b/packages/apitoolchain-api-node/src/resources/sdks.ts @@ -99,6 +99,7 @@ export interface SdkTargetCreateParams { language: ApitoolchainSdkLanguage; packageName?: string; version?: string; + sdkJson?: string; } export interface SdkVersionCreateParams { diff --git a/packages/apitoolchain-sdk-chain/chain.json b/packages/apitoolchain-sdk-chain/chain.json index 122ed6208..e4a03d70a 100644 --- a/packages/apitoolchain-sdk-chain/chain.json +++ b/packages/apitoolchain-sdk-chain/chain.json @@ -48,6 +48,12 @@ "output": "../apitoolchain-api-node", "sdkName": "api", "options": { "packageName": "@apitoolchain/api-node", "tests": false } + }, + "api-cli": { + "target": "rust-cli", + "source": "api", + "output": "../apitoolchain-api-cli", + "options": { "cliName": "apitoolchain" } } } } From 791176005402705c5d75d10ec6cc9a26766f51e0 Mon Sep 17 00:00:00 2001 From: Patryk Zdunowski Date: Sun, 2 Aug 2026 03:18:56 +0200 Subject: [PATCH 4/5] test(opencli2rust): e2e the generated CLI against a fake-data server --- .../__tests__/e2e/fake-server.test.ts | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 packages/xyd-opencli2rust/__tests__/e2e/fake-server.test.ts diff --git a/packages/xyd-opencli2rust/__tests__/e2e/fake-server.test.ts b/packages/xyd-opencli2rust/__tests__/e2e/fake-server.test.ts new file mode 100644 index 000000000..444928b26 --- /dev/null +++ b/packages/xyd-opencli2rust/__tests__/e2e/fake-server.test.ts @@ -0,0 +1,138 @@ +import { execSync, spawn } from 'node:child_process'; +import http from 'node:http'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +import { opencli2rust, writeProject } from '../../index'; +import { slug } from '../../src/naming'; +import { hasCargo } from './harness'; + +// Response-side e2e (the complement of openai.test.ts, which diffs the REQUESTS +// a real CLI sends): build the 1.basic sample CLI, run it against a fake-data +// API server, and assert what a user actually sees — stdout, stderr, exit codes, +// and that auth from the env reached the server. +const E2E = process.env.E2E_CLI === '1'; + +const SPEC = path.join(__dirname, '../../__fixtures__/1.basic/input.json'); + +/** A fake "models" API: a list, one item, 404s for the rest. */ +class FakeApiServer { + private server: http.Server; + port = 0; + lastAuth: string | undefined; + + constructor() { + this.server = http.createServer((req, res) => { + this.lastAuth = req.headers.authorization; + const respond = (status: number, body: unknown) => { + res.writeHead(status, { 'content-type': 'application/json' }); + res.end(JSON.stringify(body)); + }; + if (req.url === '/models') { + respond(200, { object: 'list', data: [{ id: 'gpt-4', owned_by: 'acme' }, { id: 'gpt-3.5', owned_by: 'acme' }] }); + } else if (req.url === '/models/gpt-4') { + respond(200, { id: 'gpt-4', object: 'model', owned_by: 'acme' }); + } else { + respond(404, { error: { message: 'model not found', type: 'invalid_request_error' } }); + } + }); + } + + start(): Promise { + return new Promise((r) => + this.server.listen(0, '127.0.0.1', () => { + this.port = (this.server.address() as { port: number }).port; + r(); + }), + ); + } + + stop() { + this.server.close(); + } +} + +interface RunResult { + code: number | null; + stdout: string; + stderr: string; +} + +function run(binPath: string, args: string[], env: NodeJS.ProcessEnv): Promise { + return new Promise((resolve) => { + const p = spawn(binPath, args, { env }); + let stdout = ''; + let stderr = ''; + p.stdout.on('data', (c) => (stdout += c)); + p.stderr.on('data', (c) => (stderr += c)); + const t = setTimeout(() => p.kill('SIGKILL'), 15000); + p.on('close', (code) => { + clearTimeout(t); + resolve({ code, stdout, stderr }); + }); + }); +} + +describe.runIf(E2E)('e2e: generated CLI against a fake-data server', () => { + const server = new FakeApiServer(); + let tmpDir = ''; + let binPath = ''; + let env: NodeJS.ProcessEnv; + + beforeAll(async () => { + if (!hasCargo()) throw new Error('cargo toolchain not available'); + await server.start(); + + const spec = JSON.parse(fs.readFileSync(SPEC, 'utf8')); + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'o2r-fake-')); + await writeProject(opencli2rust(spec), tmpDir); + const targetDir = process.env.CARGO_TARGET_DIR || path.join(tmpDir, 'target'); + execSync('cargo build --quiet', { cwd: tmpDir, stdio: 'pipe', env: { ...process.env, CARGO_TARGET_DIR: targetDir } }); + binPath = path.join(targetDir, 'debug', slug(spec.info?.title || 'cli')); + + env = { + ...process.env, + SAMPLE_API_BASE_URL: `http://127.0.0.1:${server.port}`, + SAMPLE_API_API_KEY: 'sk-fake-e2e', + }; + }, 600000); + + afterAll(() => { + server.stop(); + if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it('lists fake data as pretty JSON and exits 0', async () => { + const r = await run(binPath, ['models', 'list'], env); + expect(r.code, r.stderr).toBe(0); + const parsed = JSON.parse(r.stdout); + expect(parsed.object).toBe('list'); + expect(parsed.data.map((m: { id: string }) => m.id)).toEqual(['gpt-4', 'gpt-3.5']); + // Pretty-printed (the CliOverrides default), not a single-line dump. + expect(r.stdout).toContain('\n '); + }); + + it('retrieves one item by positional arg and sends bearer auth from the env', async () => { + const r = await run(binPath, ['models', 'retrieve', 'gpt-4'], env); + expect(r.code, r.stderr).toBe(0); + expect(JSON.parse(r.stdout)).toEqual({ id: 'gpt-4', object: 'model', owned_by: 'acme' }); + expect(server.lastAuth).toBe('Bearer sk-fake-e2e'); + }); + + it('surfaces API errors on stderr with a non-zero exit', async () => { + const r = await run(binPath, ['models', 'retrieve', 'does-not-exist'], env); + expect(r.code).toBe(1); + expect(r.stdout).toBe(''); + expect(r.stderr).toContain('request failed: 404'); + expect(r.stderr).toContain('model not found'); + }); + + it('rejects unknown commands via clap without contacting the server', async () => { + const r = await run(binPath, ['nonsense'], env); + expect(r.code).not.toBe(0); + expect(r.stderr).toContain('unrecognized subcommand'); + }); +}); From 114a777f5446302d3234191437cbaa84ea6d277b Mon Sep 17 00:00:00 2001 From: Patryk Zdunowski Date: Sun, 2 Aug 2026 03:18:57 +0200 Subject: [PATCH 5/5] feat(cli): opt-in opensdk component via components install --- .../13.api-definitions/OpenSdkGeneration.md | 5 + .dev/docs/3.cli/InstallationAndCli.md | 21 +++ .../xyd-cli/src/__tests__/bundle-size.test.ts | 80 +++++++++ .../src/__tests__/completion-parser.test.ts | 1 + .../src/__tests__/components-opensdk.test.ts | 85 ++++++++++ packages/xyd-cli/src/cli.ts | 14 ++ packages/xyd-cli/src/commands/components.ts | 25 +++ packages/xyd-cli/src/commands/index.ts | 3 +- packages/xyd-cli/src/commands/opensdk.ts | 11 ++ packages/xyd-cli/src/components/opensdk.ts | 157 ++++++++++++++++++ packages/xyd-cli/src/spec.ts | 18 ++ 11 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 packages/xyd-cli/src/__tests__/bundle-size.test.ts create mode 100644 packages/xyd-cli/src/__tests__/components-opensdk.test.ts create mode 100644 packages/xyd-cli/src/commands/opensdk.ts create mode 100644 packages/xyd-cli/src/components/opensdk.ts diff --git a/.dev/docs/13.api-definitions/OpenSdkGeneration.md b/.dev/docs/13.api-definitions/OpenSdkGeneration.md index d6aafa8f8..a079087f9 100644 --- a/.dev/docs/13.api-definitions/OpenSdkGeneration.md +++ b/.dev/docs/13.api-definitions/OpenSdkGeneration.md @@ -179,6 +179,11 @@ Representative generated layout (go): `go.mod`, `client.go`, `types.go`, `` passes through to it (the default `xyd` install ships none of it — +see `3.cli/InstallationAndCli.md` § Optional Components). + | Command | Purpose / key flags | |---------|---------------------| | `opensdk parse` | OpenAPI → IR JSON. `--spec` (required), `--output`, `--sdk-name`, `--grouping` | diff --git a/.dev/docs/3.cli/InstallationAndCli.md b/.dev/docs/3.cli/InstallationAndCli.md index 5e122d6d1..893c8520f 100644 --- a/.dev/docs/3.cli/InstallationAndCli.md +++ b/.dev/docs/3.cli/InstallationAndCli.md @@ -147,6 +147,27 @@ publish = ".xyd/build/client" } ``` +## Optional Components + +Heavy toolchains are NOT bundled with the CLI — the default install stays lean. They are +installed on demand into `~/.config/xyd/components/` (override: `XYD_COMPONENTS_DIR`) and +surface as new `xyd` subcommands: + +```bash +xyd components install opensdk # downloads @xyd-js/opensdk-cli into the components dir +xyd opensdk generate --lang typescript --spec ./openapi.yaml # passthrough to the toolchain +xyd components uninstall opensdk # removes it again +``` + +Before installation, `xyd opensdk ...` prints an install hint and exits non-zero. Under +`XYD_DEV_MODE=1` the component resolves from the monorepo build instead of npm. The +"lean by default" contract is enforced by tests (`packages/xyd-cli/src/__tests__/bundle-size.test.ts`: +dist budget, zero opensdk dependencies, footprint appears only after install). See +`13.api-definitions/OpenSdkGeneration.md` for the toolchain itself. + +(`xyd components install diagrams` remains the docs-project component flow — it installs +rendering packages into `.xyd/host`, not CLI toolchains.) + ## Advanced Usage ### Development Mode diff --git a/packages/xyd-cli/src/__tests__/bundle-size.test.ts b/packages/xyd-cli/src/__tests__/bundle-size.test.ts new file mode 100644 index 000000000..2e85fca3f --- /dev/null +++ b/packages/xyd-cli/src/__tests__/bundle-size.test.ts @@ -0,0 +1,80 @@ +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +// The "lean by default" contract of the opensdk component: the shipped CLI +// bundle must not grow with opensdk code or dependencies — the footprint may +// only appear AFTER an explicit `xyd components install opensdk`. + +const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const distDir = path.join(pkgRoot, 'dist'); + +/** Total dist JS budget. Current: ~73 KB; headroom for growth, but a hard stop + * against accidentally bundling a toolchain (opensdk's dist alone is ~250 KB + * before its 16-package dependency tree). */ +const DIST_BUDGET_BYTES = 120 * 1024; + +function distJsBytes(): number { + return fs + .readdirSync(distDir) + .filter((f) => f.endsWith('.js')) + .reduce((sum, f) => sum + fs.statSync(path.join(distDir, f)).size, 0); +} + +function dirBytes(dir: string): number { + let sum = 0; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + sum += entry.isDirectory() ? dirBytes(full) : fs.statSync(full).size; + } + return sum; +} + +describe('default CLI bundle stays lean', () => { + it(`dist JS stays under the ${DIST_BUDGET_BYTES / 1024} KB budget`, () => { + expect(fs.existsSync(distDir), 'dist not built — run `pnpm build` first').toBe(true); + expect(distJsBytes()).toBeLessThan(DIST_BUDGET_BYTES); + }); + + it('has no opensdk dependencies', () => { + const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8')); + const deps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }); + expect(deps.filter((d) => /opensdk/i.test(d))).toEqual([]); + }); + + it('bundles no opensdk implementation', () => { + // The literal '@xyd-js/opensdk-cli' package NAME is expected (the + // installer references it); actual toolchain code must never be bundled. + const bundle = fs.readFileSync(path.join(distDir, 'index.js'), 'utf8'); + expect(bundle).not.toContain('generateFileMap'); + expect(bundle).not.toContain('registerEmitter'); + }); +}); + +describe('footprint only grows after `components install opensdk`', () => { + it('the component dir materializes only on install', () => { + const componentsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'xyd-size-')); + try { + const opensdkDir = path.join(componentsDir, 'opensdk'); + expect(fs.existsSync(opensdkDir)).toBe(false); + + const r = spawnSync(process.execPath, [path.join(distDir, 'index.js'), 'components', 'install', 'opensdk'], { + encoding: 'utf8' as const, + env: { ...process.env, XYD_DEV_MODE: '1', XYD_COMPONENTS_DIR: componentsDir }, + }); + expect(r.status, r.stderr).toBe(0); + + // Dev-mode installs are manifest-only; a published install pulls the + // full @xyd-js/opensdk-cli tree (~250 KB dist + transitive deps) + // into this same dir. Either way: absent before, non-empty after. + expect(fs.existsSync(opensdkDir)).toBe(true); + expect(dirBytes(opensdkDir)).toBeGreaterThan(0); + } finally { + fs.rmSync(componentsDir, { recursive: true, force: true }); + } + }); +}); diff --git a/packages/xyd-cli/src/__tests__/completion-parser.test.ts b/packages/xyd-cli/src/__tests__/completion-parser.test.ts index f70e03d80..41636db19 100644 --- a/packages/xyd-cli/src/__tests__/completion-parser.test.ts +++ b/packages/xyd-cli/src/__tests__/completion-parser.test.ts @@ -40,6 +40,7 @@ describe('cliToOpencli', () => { name: 'component', required: true, description: 'Name of the component to install', + acceptedValues: ['diagrams', 'opensdk'], metadata: [{ name: 'example', value: 'diagrams' }], }, ]); diff --git a/packages/xyd-cli/src/__tests__/components-opensdk.test.ts b/packages/xyd-cli/src/__tests__/components-opensdk.test.ts new file mode 100644 index 000000000..98480c64a --- /dev/null +++ b/packages/xyd-cli/src/__tests__/components-opensdk.test.ts @@ -0,0 +1,85 @@ +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +// The opensdk component contract, driven through the REAL built bin: before +// `components install opensdk` the `opensdk` command is a friendly error; after, +// it passes through to the actual @xyd-js/opensdk-cli. Offline: XYD_DEV_MODE=1 +// resolves the toolchain from the monorepo build (no npm), and XYD_COMPONENTS_DIR +// points at a temp dir so nothing touches ~/.config/xyd. + +const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const CLI = path.join(pkgRoot, 'dist/index.js'); +const DEV_BIN = path.resolve(pkgRoot, '../xyd-opensdk-cli/dist/cli.js'); + +let componentsDir: string; + +function runCli(args: string[]) { + return spawnSync(process.execPath, [CLI, ...args], { + encoding: 'utf8' as const, + env: { ...process.env, XYD_DEV_MODE: '1', XYD_COMPONENTS_DIR: componentsDir }, + }); +} + +beforeAll(() => { + // Root `pretest:unit` runs `pnpm build`, so both dists exist locally and in CI. + if (!fs.existsSync(CLI) || !fs.existsSync(DEV_BIN)) { + throw new Error('dist not built — run `pnpm build` first (root pretest:unit does this).'); + } + componentsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'xyd-opensdk-')); +}); + +afterAll(() => { + fs.rmSync(componentsDir, { recursive: true, force: true }); +}); + +describe('xyd opensdk (component passthrough)', () => { + it('is a friendly error before install', () => { + const r = runCli(['opensdk', '--help']); + expect(r.status).not.toBe(0); + expect(r.stderr).toContain('components install opensdk'); + }); + + it("intercepts opensdk's own flags before the arg parser", () => { + // `--lang` is unknown to xyd's `arg` spec — without the pre-parse + // intercept this would crash with ARG_UNKNOWN_OPTION instead of the + // friendly install hint. + const r = runCli(['opensdk', 'generate', '--lang', 'go']); + expect(r.status).not.toBe(0); + expect(r.stderr).toContain('components install opensdk'); + expect(r.stderr).not.toContain('ARG_UNKNOWN_OPTION'); + expect(r.stderr).not.toContain('Unknown or unexpected option'); + }); + + it('components install opensdk succeeds in dev mode', () => { + const r = runCli(['components', 'install', 'opensdk']); + expect(r.status, r.stderr).toBe(0); + const manifest = JSON.parse(fs.readFileSync(path.join(componentsDir, 'opensdk', 'component.json'), 'utf8')); + expect(manifest.name).toBe('opensdk'); + expect(manifest.mode).toBe('dev'); + expect(fs.existsSync(manifest.binPath)).toBe(true); + }); + + it('passes through to the real opensdk CLI after install', () => { + const r = runCli(['opensdk', '--help']); + expect(r.status, r.stderr).toBe(0); + // The actual Commander help of @xyd-js/opensdk-cli — proves a real spawn. + expect(r.stdout).toContain('Usage: opensdk'); + expect(r.stdout).toContain('generate'); + expect(r.stdout).toContain('parse'); + }); + + it('components uninstall opensdk removes it and the error returns', () => { + const r = runCli(['components', 'uninstall', 'opensdk']); + expect(r.status, r.stderr).toBe(0); + expect(fs.existsSync(path.join(componentsDir, 'opensdk'))).toBe(false); + + const after = runCli(['opensdk', '--help']); + expect(after.status).not.toBe(0); + expect(after.stderr).toContain('components install opensdk'); + }); +}); diff --git a/packages/xyd-cli/src/cli.ts b/packages/xyd-cli/src/cli.ts index a3c929b98..2f902a51a 100755 --- a/packages/xyd-cli/src/cli.ts +++ b/packages/xyd-cli/src/cli.ts @@ -22,6 +22,14 @@ export async function cli(argv = process.argv.slice(2)) { process.env.XYD_CLI = 'true'; + // `opensdk` is a passthrough to an optionally installed toolchain whose own + // flags (e.g. `--lang`) `arg` would reject as unknown — hand it the raw + // argv before any parsing. (argv.slice(1), not process.argv: respects the + // injectable `argv` parameter of cli().) + if (argv[0] === 'opensdk') { + return globalCommands.opensdk(argv.slice(1)); + } + const { globalFlags, commands } = parseArgs(argv); if (globalFlags.help) { @@ -50,6 +58,12 @@ export async function cli(argv = process.argv.slice(2)) { // Handle subcommands for components await globalCommands.components(commandArgs, globalFlags); break; + case 'opensdk': + // Reached only when a known global flag preceded the command (e.g. + // `xyd --verbose opensdk gen`) — globals are consumed by `arg` and + // not forwarded; the canonical form is `xyd opensdk ...` first. + await globalCommands.opensdk(commandArgs); + break; case 'completion': // Pass the raw args array (shell name / subcommand) await globalCommands.completion(commandArgs, globalFlags); diff --git a/packages/xyd-cli/src/commands/components.ts b/packages/xyd-cli/src/commands/components.ts index c47b17b70..0bd80fd3f 100644 --- a/packages/xyd-cli/src/commands/components.ts +++ b/packages/xyd-cli/src/commands/components.ts @@ -2,6 +2,8 @@ import colors from 'picocolors'; import { componentsInstall } from '@xyd-js/documan'; +import { installOpensdk, uninstallOpensdk } from '../components/opensdk'; + /** A `components ` handler. Receives the args *after* the subcommand. */ type ComponentsSubcommand = (args: string[], flags: Record) => Promise; @@ -15,11 +17,34 @@ const subcommands = { install: async (args) => { const componentName = args[0]; + // CLI-level components (new toolchain commands) install into the + // user-global components dir; docs-project components (diagrams) + // delegate to documan's host-scoped installer. + if (componentName === 'opensdk') { + const ok = await installOpensdk(); + if (!ok) { + process.exit(1); + } + return; + } + const success = await componentsInstall(componentName); if (!success) { process.exit(1); } }, + // `components uninstall ` + uninstall: async (args) => { + const componentName = args[0]; + if (componentName !== 'opensdk') { + console.error(colors.red(`Error: '${componentName ?? ''}' cannot be uninstalled (only: opensdk).`)); + process.exit(1); + return; + } + if (!uninstallOpensdk()) { + process.exit(1); + } + }, } satisfies Record; type ComponentsSubcommandName = keyof typeof subcommands; diff --git a/packages/xyd-cli/src/commands/index.ts b/packages/xyd-cli/src/commands/index.ts index 543aa0f03..3fc493a1b 100755 --- a/packages/xyd-cli/src/commands/index.ts +++ b/packages/xyd-cli/src/commands/index.ts @@ -4,4 +4,5 @@ export { serve } from './serve'; export { install } from './install'; export { migrateme } from './migrateme'; export { components } from './components'; -export { completion } from './completion'; \ No newline at end of file +export { completion } from './completion'; +export { opensdk } from './opensdk'; \ No newline at end of file diff --git a/packages/xyd-cli/src/commands/opensdk.ts b/packages/xyd-cli/src/commands/opensdk.ts new file mode 100644 index 000000000..12ead6042 --- /dev/null +++ b/packages/xyd-cli/src/commands/opensdk.ts @@ -0,0 +1,11 @@ +import { runOpensdk } from '../components/opensdk'; + +/** + * `xyd opensdk ` — passthrough to the optionally-installed opensdk + * toolchain (`xyd components install opensdk`). Receives the RAW argv after + * `opensdk` (never parsed by `arg`: opensdk's own flags like `--lang` would be + * rejected as unknown), and never returns (exit code propagates). + */ +export async function opensdk(args: string[]): Promise { + runOpensdk(args); +} diff --git a/packages/xyd-cli/src/components/opensdk.ts b/packages/xyd-cli/src/components/opensdk.ts new file mode 100644 index 000000000..de08500ce --- /dev/null +++ b/packages/xyd-cli/src/components/opensdk.ts @@ -0,0 +1,157 @@ +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import colors from 'picocolors'; + +// The `opensdk` CLI component: the @xyd-js/opensdk-cli toolchain, installed ON +// DEMAND (`xyd components install opensdk`) so the default CLI stays lean — the +// published opensdk tree (all language emitters + the OpenAPI pipeline) is heavy +// and must never ship with `xyd` itself. State and payload live together in a +// self-contained component dir; nothing here imports any opensdk code. + +const OPENSDK_PACKAGE = '@xyd-js/opensdk-cli'; + +interface OpensdkManifest { + name: 'opensdk'; + package: string; + version: string; + mode: 'dev' | 'published'; + binPath: string; + installedAt: string; +} + +/** Where CLI components live: user-global (survives CLI upgrades, permission-safe + * under root-owned npm prefixes — same home as shell completions), overridable + * via XYD_COMPONENTS_DIR (the test seam). */ +export function componentsBaseDir(): string { + return process.env.XYD_COMPONENTS_DIR || path.join(os.homedir(), '.config', 'xyd', 'components'); +} + +export function opensdkComponentDir(): string { + return path.join(componentsBaseDir(), 'opensdk'); +} + +function manifestPath(): string { + return path.join(opensdkComponentDir(), 'component.json'); +} + +function readManifest(): OpensdkManifest | null { + try { + return JSON.parse(fs.readFileSync(manifestPath(), 'utf8')) as OpensdkManifest; + } catch { + return null; + } +} + +/** The installed opensdk bin, or null when the component isn't (validly) installed. */ +export function resolveOpensdkBin(): string | null { + const manifest = readManifest(); + if (!manifest?.binPath) return null; + return fs.existsSync(manifest.binPath) ? manifest.binPath : null; +} + +/** Dev mode: the monorepo's built opensdk-cli, found by walking up from this module. */ +function findMonorepoOpensdkBin(): string | null { + let dir = path.dirname(fileURLToPath(import.meta.url)); + for (let i = 0; i < 6; i++) { + const candidate = path.join(dir, 'packages', 'xyd-opensdk-cli', 'dist', 'cli.js'); + if (fs.existsSync(candidate)) return candidate; + const parent = path.dirname(dir); + if (parent === dir) break; + dir = parent; + } + return null; +} + +export async function installOpensdk(): Promise { + const existing = resolveOpensdkBin(); + if (existing) { + console.log(colors.green(`✓ opensdk is already installed (${existing})`)); + return true; + } + + const dir = opensdkComponentDir(); + fs.mkdirSync(dir, { recursive: true }); + + let binPath: string; + let mode: OpensdkManifest['mode']; + let version = 'latest'; + + if (process.env.XYD_DEV_MODE) { + // Dev mode: no npm — point at the monorepo build (external deps resolve + // through the monorepo's own node_modules, relative to the module file). + const devBin = findMonorepoOpensdkBin(); + if (!devBin) { + console.error( + colors.red('XYD_DEV_MODE is set but packages/xyd-opensdk-cli/dist/cli.js was not found — run `pnpm build` first.'), + ); + return false; + } + binPath = devBin; + mode = 'dev'; + version = 'workspace'; + } else { + console.log(`Installing ${OPENSDK_PACKAGE}...`); + fs.writeFileSync( + path.join(dir, 'package.json'), + `${JSON.stringify({ name: 'xyd-component-opensdk', private: true, dependencies: { [OPENSDK_PACKAGE]: version } }, null, 2)}\n`, + ); + const { nodeInstallPackages } = await import('@xyd-js/documan'); + try { + await nodeInstallPackages(dir); + } catch (err) { + console.error(colors.red(`Failed to install ${OPENSDK_PACKAGE}: ${err instanceof Error ? err.message : err}`)); + return false; + } + binPath = path.join(dir, 'node_modules', '@xyd-js', 'opensdk-cli', 'dist', 'cli.js'); + mode = 'published'; + if (!fs.existsSync(binPath)) { + console.error(colors.red(`Install finished but the opensdk bin is missing at ${binPath}.`)); + return false; + } + } + + const manifest: OpensdkManifest = { + name: 'opensdk', + package: OPENSDK_PACKAGE, + version, + mode, + binPath, + installedAt: new Date().toISOString(), + }; + fs.writeFileSync(manifestPath(), `${JSON.stringify(manifest, null, 2)}\n`); + + console.log(colors.green('✓ opensdk installed.')); + console.log('Run `xyd opensdk --help` to get started.'); + return true; +} + +export function uninstallOpensdk(): boolean { + const dir = opensdkComponentDir(); + if (!fs.existsSync(dir)) { + console.log('opensdk is not installed — nothing to remove.'); + return true; + } + fs.rmSync(dir, { recursive: true, force: true }); + console.log(colors.green('✓ opensdk uninstalled.')); + return true; +} + +/** Spawn the installed opensdk CLI with raw args, propagating the exit code. */ +export function runOpensdk(args: string[]): never { + const bin = resolveOpensdkBin(); + if (!bin) { + console.error(colors.red('The opensdk toolchain is not installed.')); + console.error(`Install it with: ${colors.bold('xyd components install opensdk')}`); + process.exit(1); + } + const result = spawnSync(process.execPath, [bin, ...args], { stdio: 'inherit' }); + if (result.error) { + console.error(colors.red(`Failed to run opensdk: ${result.error.message}`)); + process.exit(1); + } + process.exit(result.status ?? 1); +} diff --git a/packages/xyd-cli/src/spec.ts b/packages/xyd-cli/src/spec.ts index e24831bf9..9e8d26296 100755 --- a/packages/xyd-cli/src/spec.ts +++ b/packages/xyd-cli/src/spec.ts @@ -50,11 +50,29 @@ export const cliSpec: CLI = { required: true, description: 'Name of the component to install', example: 'diagrams', + acceptedValues: ['diagrams', 'opensdk'], + }, + ], + }, + uninstall: { + description: 'Remove an installed xyd component', + usage: 'xyd components uninstall [flags]', + arguments: [ + { + name: 'component', + required: true, + description: 'Name of the component to remove', + example: 'opensdk', + acceptedValues: ['opensdk'], }, ], }, }, }, + opensdk: { + description: 'Run the OpenSDK toolchain (requires `xyd components install opensdk`)', + usage: 'xyd opensdk [flags]', + }, completion: { description: 'Generate shell completions or the CLI OpenCLI document. ' +