Skip to content

Commit 0849119

Browse files
committed
update build system and CI workflows
1 parent 97ffb1e commit 0849119

File tree

5 files changed

+202
-9
lines changed

5 files changed

+202
-9
lines changed

.github/scripts/changelog.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ esac
3232

3333
# Function to generate auto changelog
3434
generate_auto_changelog() {
35-
local current_date=$(date +'%Y-%m-%d %H:%M:%S UTC')
35+
local current_date=$(date +'%Y-%-m-%-d %H:%M:%S UTC') # Remove leading zeros
3636
local short_commit=$(git rev-parse --short HEAD)
3737
local previous_tag=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo 'main')
3838

@@ -42,7 +42,7 @@ generate_auto_changelog() {
4242
This is an automated ${RELEASE_TYPE,,} based on the date ${TAG_VERSION}.
4343
4444
### What's Changed
45-
- Latest changes from the main branch as of $(date +'%Y-%m-%d')
45+
- Latest changes from the main branch as of $(date +'%Y-%-m-%-d')
4646
- Generated automatically from commit ${short_commit}
4747
4848
### Release Details
@@ -90,7 +90,7 @@ extract_existing_changelog() {
9090
# Function to enhance existing changelog with release details
9191
enhance_changelog() {
9292
local existing_changelog="$1"
93-
local current_date=$(date +'%Y-%m-%d %H:%M:%S UTC')
93+
local current_date=$(date +'%Y-%-m-%-d %H:%M:%S UTC') # Remove leading zeros
9494
local short_commit=$(git rev-parse --short HEAD)
9595

9696
cat << EOF

.github/workflows/ci.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
~/.cargo/registry
4646
~/.cargo/git
4747
target
48-
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
48+
key: ${{ runner.os }}-clippy-cargo-${{ hashFiles('Cargo.lock') }}
4949

5050
- name: Run clippy
5151
run: cargo clippy --all-targets --all-features -- -D warnings
@@ -132,4 +132,28 @@ jobs:
132132
- name: Build documentation
133133
run: cargo doc --locked --no-deps --all-features
134134
env:
135-
RUSTDOCFLAGS: -D warnings
135+
RUSTDOCFLAGS: -D warnings
136+
137+
- name: Check for broken links in docs
138+
run: |
139+
# Install cargo-deadlinks if not cached
140+
if ! command -v cargo-deadlinks &> /dev/null; then
141+
cargo install cargo-deadlinks
142+
fi
143+
cargo deadlinks --check-http
144+
145+
security-audit:
146+
name: Security Audit
147+
runs-on: ubuntu-latest
148+
steps:
149+
- name: Checkout
150+
uses: actions/checkout@v4
151+
152+
- name: Install Rust stable
153+
uses: dtolnay/rust-toolchain@stable
154+
155+
- name: Install cargo-audit
156+
run: cargo install cargo-audit
157+
158+
- name: Run security audit
159+
run: cargo audit

.github/workflows/release.yml

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ jobs:
5151
- name: Generate version and tag
5252
id: version
5353
run: |
54-
# Generate date-based version (YYYY.MM.DD)
55-
DATE_VERSION=$(date +'%Y.%m.%d')
54+
# Generate date-based version (YYYY.M.D) - no leading zeros
55+
YEAR=$(date +'%Y')
56+
MONTH=$(date +'%-m') # %-m removes leading zero
57+
DAY=$(date +'%-d') # %-d removes leading zero
58+
DATE_VERSION="${YEAR}.${MONTH}.${DAY}"
5659
TAG_NAME="v${DATE_VERSION}"
5760
5861
echo "VERSION=${DATE_VERSION}" >> $GITHUB_OUTPUT
@@ -162,16 +165,23 @@ jobs:
162165
163166
# Delete existing tag if it exists (for force mode)
164167
if git tag -l | grep -q "^${TAG_NAME}$"; then
168+
echo "Deleting existing tag ${TAG_NAME}"
165169
git tag -d "${TAG_NAME}"
166-
git push origin ":refs/tags/${TAG_NAME}" || true
170+
# Delete remote tag, ignore if it doesn't exist
171+
git push origin ":refs/tags/${TAG_NAME}" 2>/dev/null || echo "Remote tag ${TAG_NAME} doesn't exist or already deleted"
167172
fi
168173
169174
# Create new tag
170175
git tag -a "${TAG_NAME}" -m "${TAG_MSG}"
171176
172-
# Push changes and tag
177+
# Push changes and tag with error handling
178+
echo "Pushing commit to main branch..."
173179
git push origin main
180+
181+
echo "Pushing tag ${TAG_NAME}..."
174182
git push origin "${TAG_NAME}"
183+
184+
echo "✅ Successfully pushed commit and tag"
175185
176186
- name: Generate changelog
177187
if: steps.check.outputs.should_release == 'true'
@@ -312,10 +322,24 @@ jobs:
312322
- name: Build binary
313323
run: cargo build --locked --release --target ${{ matrix.target }}
314324

325+
- name: Prepare binary for upload
326+
run: |
327+
# Verify binary exists and is executable
328+
BINARY_PATH="target/${{ matrix.target }}/release/${{ matrix.artifact_name }}"
329+
if [ ! -f "$BINARY_PATH" ]; then
330+
echo "❌ Binary not found at $BINARY_PATH"
331+
exit 1
332+
fi
333+
334+
# Get binary size for logging
335+
BINARY_SIZE=$(stat -c%s "$BINARY_PATH" 2>/dev/null || stat -f%z "$BINARY_PATH" 2>/dev/null || echo "unknown")
336+
echo "📦 Binary ready: $BINARY_PATH (${BINARY_SIZE} bytes)"
337+
315338
- name: Upload binary to release
316339
uses: softprops/action-gh-release@v2
317340
with:
318341
tag_name: ${{ needs.check-and-release.outputs.tag_name }}
319342
files: target/${{ matrix.target }}/release/${{ matrix.artifact_name }}
343+
fail_on_unmatched_files: true
320344
env:
321345
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

build.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ impl RefResolver {
163163
// Clean up any remaining unresolved references
164164
self.clean_unresolved_refs(&mut value)?;
165165

166+
// Deduplicate response types to prevent progenitor 0.11.0 assertion failures
167+
self.deduplicate_response_types(&mut value)?;
168+
166169
Ok(value)
167170
}
168171

@@ -622,6 +625,131 @@ properties:
622625
}
623626
Ok(())
624627
}
628+
629+
fn deduplicate_response_types(&self, value: &mut Value) -> Result<(), Box<dyn std::error::Error>> {
630+
println!("Deduplicating response types to prevent progenitor assertion failures...");
631+
let mut operations_modified = 0;
632+
let mut total_responses_removed = 0;
633+
634+
if let Some(obj) = value.as_mapping_mut() {
635+
if let Some(paths) = obj.get_mut(&Value::String("paths".to_string())) {
636+
if let Some(paths_map) = paths.as_mapping_mut() {
637+
for (path_key, path_value) in paths_map.iter_mut() {
638+
if let Some(path_obj) = path_value.as_mapping_mut() {
639+
// Check each HTTP method in this path
640+
for (method_key, method_value) in path_obj.iter_mut() {
641+
if let Some(method_str) = method_key.as_str() {
642+
// Skip non-HTTP method keys like "parameters"
643+
if !["get", "post", "put", "patch", "delete", "head", "options", "trace"].contains(&method_str) {
644+
continue;
645+
}
646+
647+
if let Some(operation) = method_value.as_mapping_mut() {
648+
// Get operation_id before mutable borrow
649+
let operation_id = operation
650+
.get(&Value::String("operationId".to_string()))
651+
.and_then(|v| v.as_str())
652+
.unwrap_or("unknown")
653+
.to_string();
654+
655+
if let Some(responses) = operation.get_mut(&Value::String("responses".to_string())) {
656+
if let Some(responses_map) = responses.as_mapping_mut() {
657+
658+
let original_count = responses_map.len();
659+
let mut success_responses = Vec::new();
660+
let mut other_responses = Vec::new();
661+
662+
// Separate success (2xx) responses from others
663+
for (status_key, response_value) in responses_map.iter() {
664+
if let Some(status_str) = status_key.as_str() {
665+
if status_str.starts_with('2') && status_str.len() == 3 {
666+
success_responses.push((status_key.clone(), response_value.clone()));
667+
} else {
668+
other_responses.push((status_key.clone(), response_value.clone()));
669+
}
670+
} else {
671+
other_responses.push((status_key.clone(), response_value.clone()));
672+
}
673+
}
674+
675+
// Use a more aggressive approach: if there are multiple success responses,
676+
// or if there's any response complexity, simplify to just one response
677+
if success_responses.len() > 1 || original_count > 2 {
678+
println!("Operation '{}' ({} {}) has {} responses ({}), simplifying to prevent assertion failure",
679+
operation_id, method_str.to_uppercase(),
680+
path_key.as_str().unwrap_or("unknown"),
681+
success_responses.len(),
682+
original_count);
683+
684+
responses_map.clear();
685+
686+
// Keep only one response: prefer first success, then first error, then default
687+
if let Some((first_status, first_response)) = success_responses.into_iter().next() {
688+
// Also simplify the response content to avoid multiple content types
689+
let mut simplified_response = first_response;
690+
if let Some(response_obj) = simplified_response.as_mapping_mut() {
691+
if let Some(content) = response_obj.get_mut(&Value::String("content".to_string())) {
692+
if let Some(content_map) = content.as_mapping_mut() {
693+
// Keep only the first content type to avoid multiple response types
694+
if content_map.len() > 1 {
695+
let first_content_type = content_map.keys().next().cloned();
696+
if let Some(first_key) = first_content_type {
697+
let first_value = content_map.get(&first_key).cloned();
698+
content_map.clear();
699+
if let Some(value) = first_value {
700+
content_map.insert(first_key, value);
701+
}
702+
}
703+
}
704+
}
705+
}
706+
}
707+
responses_map.insert(first_status, simplified_response);
708+
} else if let Some((first_status, first_response)) = other_responses.iter()
709+
.find(|(status_key, _)| {
710+
if let Some(status_str) = status_key.as_str() {
711+
status_str != "default"
712+
} else {
713+
true
714+
}
715+
})
716+
.map(|(k, v)| (k.clone(), v.clone())) {
717+
responses_map.insert(first_status, first_response);
718+
} else if let Some((default_status, default_response)) = other_responses.iter()
719+
.find(|(status_key, _)| {
720+
if let Some(status_str) = status_key.as_str() {
721+
status_str == "default"
722+
} else {
723+
false
724+
}
725+
})
726+
.map(|(k, v)| (k.clone(), v.clone())) {
727+
responses_map.insert(default_status, default_response);
728+
}
729+
730+
operations_modified += 1;
731+
total_responses_removed += original_count - responses_map.len();
732+
}
733+
}
734+
}
735+
}
736+
}
737+
}
738+
}
739+
}
740+
}
741+
}
742+
}
743+
744+
if operations_modified > 0 {
745+
println!("Modified {} operations, removed {} duplicate responses",
746+
operations_modified, total_responses_removed);
747+
} else {
748+
println!("No operations with multiple response types found");
749+
}
750+
751+
Ok(())
752+
}
625753
}
626754

627755
fn generate_client_code(spec: &Value) -> Result<String, Box<dyn std::error::Error>> {

examples/basic.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use rsdo::{Client, ClientInfo};
2+
3+
fn main() {
4+
println!("Creating DigitalOcean client...");
5+
6+
// Create a reqwest client
7+
let http_client = reqwest::Client::new();
8+
9+
// Create the DigitalOcean client
10+
let client = Client::new_with_client("https://api.digitalocean.com/v2", http_client);
11+
12+
println!("Client created successfully!");
13+
println!("Base URL: {}", client.baseurl());
14+
15+
// The client is ready to use - in a real application you would add authentication
16+
// and make actual API calls here
17+
}

0 commit comments

Comments
 (0)